HOW TO MAKE SINGLE CONSUMABLE IN APP PURCHASE IN ANDROID USING GOOGLE PLAY BILLING LIBRARY
Please Subscribe Youtube| Like Facebook | Follow Twitter
Introduction
In this article we will learn how to integrate and make in app purchase of Single Consumable one-time products using google play billing library.
Tips
Article | Java | Kotlin |
For Single Non-consumable one-time product | link | link |
For Multiple Non-consumable one-time product | link | link |
For Single Consumable one-time product | link | link |
For Multiple Consumable one-time product | link | link |
For Single In App Subscriptions | link | link |
For Multiple In App Subscriptions | link | link |
Our Example
In our example we have a textview which shows count of purchased consumable items and a consume button which allow user to purchase consumable item many times. When the item is purchased then we are consuming that item as well as incrementing consumable item count and saving its value (count) to preference.



Requirements:
- Android Project/App to which In App Purchase Item to be added
- Google play console account
Note: In order to create in app product consumable items in Google Console or perform real test of in app purchase consumable item in your app, you first need to Integrate Google Play Library and add billing permission in your project and then upload that apk on Google play console and publish it any track (Internal test track (recommended), Closed track, Open track, Production track). Therefore we will perform these actions first.
Steps
Follow below steps
1 Integrate Google Play Library in your project
2 Create an application and fill Store listing in Google play console
3 Upload and publish your app
4 Create Consumable item “consumable” in Google play console
5 Code in app purchase flow logic.
6 Run and Test the application
Step 1 to 3 already explained in this article so follow these steps from there.
4 Create Consumable item “consumable” in Google play console
After releasing app go to Monetize -> Products -> In-app products.
Click on create and fill necessary info then save and activate the product items.


5 Code in app purchase flow logic.
First make sure you have added Google Play Billing Library dependency and billing permission, which was described in step 1.
Then Add textviews consumable_count and consumable_button button in activity_main.xml Layout.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
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"
android:orientation="vertical"
tools:context=".MainActivity">
<TextView
android:id="@+id/consume_count"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:text=""
/>
<Button
android:id="@+id/consume_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="Consume"
android:onClick="consume"
/>
</LinearLayout>
In order to make In-App purchase our MainActivity needs to implements PurchasesUpdatedListener interface and override onPurchasesUpdated() method which will be called every time to get notifications for purchases updates. Both purchases initiated by your app and the ones initiated outside of your app will be reported here.
We also need to create BillingClient Object and set listener to it so that we can communicate with Google Play Billing Service.
public class MainActivity extends AppCompatActivity implements PurchasesUpdatedListener {
private BillingClient billingClient;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
billingClient = BillingClient.newBuilder(this)
.enablePendingPurchases().setListener(this).build();
}
@Override
public void onPurchasesUpdated(BillingResult billingResult, @Nullable
List<Purchase> purchases) {
//we will code purchase result logic later
}
}
On consume button click we will initiate purchase flow
//initiate purchase on consume button click
public void consume(View view) {
//check if service is already connected
if (billingClient.isReady()) {
initiatePurchase();
}
//else reconnect service
else{
billingClient = BillingClient.newBuilder(this).enablePendingPurchases().setListener(this).build();
billingClient.startConnection(new BillingClientStateListener() {
@Override
public void onBillingSetupFinished(@NonNull BillingResult billingResult) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
initiatePurchase();
} else {
Toast.makeText(getApplicationContext(),"Error "+billingResult.getDebugMessage(),Toast.LENGTH_SHORT).show();
}
}
@Override
public void onBillingServiceDisconnected() {
}
});
}
}
In initiatePurchase() method we have included product id “consumable”, which was added in Google Play Console inside in app products, specified product type as INAPP. Then we have called querySkuDetailsAsync() method which will query our product from Play console. After querying we will call launchBillingFlow() method which will display purchase dialog. Result of purchase dialog will be reported inside onPurchasesUpdated() method which we will code next.
private void initiatePurchase() {
List<String> skuList = new ArrayList<>();
skuList.add(PRODUCT_ID);
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
params.setSkusList(skuList).setType(INAPP);
billingClient.querySkuDetailsAsync(params.build(),
new SkuDetailsResponseListener() {
@Override
public void onSkuDetailsResponse(@NonNull BillingResult billingResult,
List<SkuDetails> skuDetailsList) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
if (skuDetailsList != null && skuDetailsList.size() > 0) {
BillingFlowParams flowParams = BillingFlowParams.newBuilder()
.setSkuDetails(skuDetailsList.get(0))
.build();
billingClient.launchBillingFlow(MainActivity.this, flowParams);
}
else{
//try to add item/product id "consumable" inside managed product in google play console
Toast.makeText(getApplicationContext(),"Purchase Item not Found",Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(getApplicationContext(),
" Error "+billingResult.getDebugMessage(), Toast.LENGTH_SHORT).show();
}
}
});
}
In onPurchasesUpdated() method we will check purchase result and handle it accordingly.
@Override
public void onPurchasesUpdated(BillingResult billingResult, @Nullable List<Purchase> purchases) {
//if item newly purchased
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && purchases != null) {
handlePurchases(purchases);
}
//if item already purchased then check and reflect changes
else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED) {
Purchase.PurchasesResult queryAlreadyPurchasesResult = billingClient.queryPurchases(INAPP);
List<Purchase> alreadyPurchases = queryAlreadyPurchasesResult.getPurchasesList();
if(alreadyPurchases!=null){
handlePurchases(alreadyPurchases);
}
}
//if purchase cancelled
else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.USER_CANCELED) {
Toast.makeText(getApplicationContext(),"Purchase Canceled",Toast.LENGTH_SHORT).show();
}
// Handle any other error msgs
else {
Toast.makeText(getApplicationContext(),"Error "+billingResult.getDebugMessage(),Toast.LENGTH_SHORT).show();
}
}
In handlePurchases() method we will handle, verify and consume purchase. After the purchase, consume is necessary because failure to properly acknowledge or consume purchase will result in purchase being refunded. For non consumable item we have to acknowledge purchase and for consumable item we have to only consume purchase. After the consuming we will save purchase value in preference and increment consume counter.
This method also toast user to complete transaction if purchase status is pending. In case of unspecified purchase state we toast user “Purchase Status Unknown”
void handlePurchases(List<Purchase> purchases) {
for(Purchase purchase:purchases) {
//if item is purchased
if (PRODUCT_ID.equals(purchase.getSku()) && purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED)
{
if (!verifyValidSignature(purchase.getOriginalJson(), purchase.getSignature())) {
// Invalid purchase
// show error to user
Toast.makeText(getApplicationContext(), "Error : Invalid Purchase", Toast.LENGTH_SHORT).show();
return;
}
// else purchase is valid
//if item is purchased and not consumed
if (!purchase.isAcknowledged()) {
ConsumeParams consumeParams = ConsumeParams.newBuilder()
.setPurchaseToken(purchase.getPurchaseToken())
.build();
billingClient.consumeAsync(consumeParams, consumeListener);
}
}
//if purchase is pending
else if( PRODUCT_ID.equals(purchase.getSku()) && purchase.getPurchaseState() == Purchase.PurchaseState.PENDING)
{
Toast.makeText(getApplicationContext(),
"Purchase is Pending. Please complete Transaction", Toast.LENGTH_SHORT).show();
}
//if purchase is refunded or unknown
else if(PRODUCT_ID.equals(purchase.getSku()) && purchase.getPurchaseState() == Purchase.PurchaseState.UNSPECIFIED_STATE)
{
Toast.makeText(getApplicationContext(), "Purchase Status Unknown", Toast.LENGTH_SHORT).show();
}
}
}
ConsumeResponseListener consumeListener = new ConsumeResponseListener() {
@Override
public void onConsumeResponse(BillingResult billingResult, String purchaseToken) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
int consumeCountValue=getPurchaseCountValueFromPref()+1;
savePurchaseCountValueToPref(consumeCountValue);
Toast.makeText(getApplicationContext(), "Item Consumed", Toast.LENGTH_SHORT).show();
consumeCount.setText("Item Consumed "+getPurchaseCountValueFromPref()+" Time(s)");
}
}
};
For verification we have used client side signature verification, which will be performed with in app (less secure). However if you can afford server then you should do verification of purchase through your server (more secure).
In client side verification you need to add your developer’s public key of app.
For old Play Console Design To get key go to Developer Console > Select your app > Development Tools > Services & APIs.

For new Play Console Design To get key go to Developer Console > Select your app > Monetize > Monetization setup

Then paste that key to String variable base64key in below method.
/**
* Verifies that the purchase was signed correctly for this developer's public key.
* <p>Note: It's strongly recommended to perform such check on your backend since hackers can
* replace this method with "constant true" if they decompile/rebuild your app.
* </p>
*/
private boolean verifyValidSignature(String signedData, String signature) {
try {
// To get key go to Developer Console > Select your app > Development Tools > Services & APIs.
String base64Key = "Add Your Key Here"
return Security.verifyPurchase(base64Key, signedData, signature);
} catch (IOException e) {
return false;
}
}
Code of Security.java is at whole code section
Up to that point our purchase logic is complete. Now On every app start we will check purchase status of item from Google Play Store Cache using getPurchasesList() method and reflect necessary changes accordingly.
// Establish connection to billing client
//check purchase status from google play store cache on every app start
billingClient = BillingClient.newBuilder(this)
.enablePendingPurchases().setListener(this).build();
billingClient.startConnection(new BillingClientStateListener() {
@Override
public void onBillingSetupFinished(@NonNull BillingResult billingResult) {
if(billingResult.getResponseCode()==BillingClient.BillingResponseCode.OK){
Purchase.PurchasesResult queryPurchase = billingClient.queryPurchases(INAPP);
List<Purchase> queryPurchases = queryPurchase.getPurchasesList();
if(queryPurchases!=null && queryPurchases.size()>0){
handlePurchases(queryPurchases);
}
}
}
@Override
public void onBillingServiceDisconnected() {
}
});
consumeCount.setText("Item Consumed "+getPurchaseCountValueFromPref()+" Time(s)");
Whole Code
App level build.gradle
apply plugin: 'com.android.application'
android {
compileSdkVersion 30
buildToolsVersion "29.0.3"
defaultConfig {
applicationId "com.programtown.example"
minSdkVersion 17
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.2'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
implementation 'com.android.billingclient:billing:3.0.1'
}
Manifest File
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.programtown.example">
<uses-permission android:name="com.android.vending.BILLING" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
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"
android:orientation="vertical"
tools:context=".MainActivity">
<TextView
android:id="@+id/consume_count"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:text=""
/>
<Button
android:id="@+id/consume_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="Consume"
android:onClick="consume"
/>
</LinearLayout>
MainActivity.java
package com.programtown.example;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import android.content.SharedPreferences;
import androidx.annotation.Nullable;
import com.android.billingclient.api.AcknowledgePurchaseParams;
import com.android.billingclient.api.AcknowledgePurchaseResponseListener;
import com.android.billingclient.api.BillingClient;
import com.android.billingclient.api.BillingClientStateListener;
import com.android.billingclient.api.BillingFlowParams;
import com.android.billingclient.api.BillingResult;
import com.android.billingclient.api.ConsumeParams;
import com.android.billingclient.api.ConsumeResponseListener;
import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.PurchasesUpdatedListener;
import com.android.billingclient.api.SkuDetails;
import com.android.billingclient.api.SkuDetailsParams;
import com.android.billingclient.api.SkuDetailsResponseListener;
import static com.android.billingclient.api.BillingClient.SkuType.INAPP;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity implements PurchasesUpdatedListener {
public static final String PREF_FILE= "MyPref";
public static final String PURCHASE_KEY= "consumable";
public static final String PRODUCT_ID= "consumable";
Button consumeButton;
TextView consumeCount;
private BillingClient billingClient;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
consumeButton=(Button) findViewById(R.id.consume_button);
consumeCount=(TextView) findViewById(R.id.consume_count);
// Establish connection to billing client
//check purchase status from google play store cache on every app start
billingClient = BillingClient.newBuilder(this)
.enablePendingPurchases().setListener(this).build();
billingClient.startConnection(new BillingClientStateListener() {
@Override
public void onBillingSetupFinished(@NonNull BillingResult billingResult) {
if(billingResult.getResponseCode()==BillingClient.BillingResponseCode.OK){
Purchase.PurchasesResult queryPurchase = billingClient.queryPurchases(INAPP);
List<Purchase> queryPurchases = queryPurchase.getPurchasesList();
if(queryPurchases!=null && queryPurchases.size()>0){
handlePurchases(queryPurchases);
}
}
}
@Override
public void onBillingServiceDisconnected() {
}
});
consumeCount.setText("Item Consumed "+getPurchaseCountValueFromPref()+" Time(s)");
}
private SharedPreferences getPreferenceObject() {
return getApplicationContext().getSharedPreferences(PREF_FILE, 0);
}
private SharedPreferences.Editor getPreferenceEditObject() {
SharedPreferences pref = getApplicationContext().getSharedPreferences(PREF_FILE, 0);
return pref.edit();
}
private int getPurchaseCountValueFromPref(){
return getPreferenceObject().getInt( PURCHASE_KEY,0);
}
private void savePurchaseCountValueToPref(int value){
getPreferenceEditObject().putInt(PURCHASE_KEY,value).commit();
}
//initiate purchase on consume button click
public void consume(View view) {
//check if service is already connected
if (billingClient.isReady()) {
initiatePurchase();
}
//else reconnect service
else{
billingClient = BillingClient.newBuilder(this).enablePendingPurchases().setListener(this).build();
billingClient.startConnection(new BillingClientStateListener() {
@Override
public void onBillingSetupFinished(@NonNull BillingResult billingResult) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
initiatePurchase();
} else {
Toast.makeText(getApplicationContext(),"Error "+billingResult.getDebugMessage(),Toast.LENGTH_SHORT).show();
}
}
@Override
public void onBillingServiceDisconnected() {
}
});
}
}
private void initiatePurchase() {
List<String> skuList = new ArrayList<>();
skuList.add(PRODUCT_ID);
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
params.setSkusList(skuList).setType(INAPP);
billingClient.querySkuDetailsAsync(params.build(),
new SkuDetailsResponseListener() {
@Override
public void onSkuDetailsResponse(@NonNull BillingResult billingResult,
List<SkuDetails> skuDetailsList) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
if (skuDetailsList != null && skuDetailsList.size() > 0) {
BillingFlowParams flowParams = BillingFlowParams.newBuilder()
.setSkuDetails(skuDetailsList.get(0))
.build();
billingClient.launchBillingFlow(MainActivity.this, flowParams);
}
else{
//try to add item/product id "consumable" inside managed product in google play console
Toast.makeText(getApplicationContext(),"Purchase Item not Found",Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(getApplicationContext(),
" Error "+billingResult.getDebugMessage(), Toast.LENGTH_SHORT).show();
}
}
});
}
@Override
public void onPurchasesUpdated(BillingResult billingResult, @Nullable List<Purchase> purchases) {
//if item newly purchased
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && purchases != null) {
handlePurchases(purchases);
}
//if item already purchased then check and reflect changes
else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED) {
Purchase.PurchasesResult queryAlreadyPurchasesResult = billingClient.queryPurchases(INAPP);
List<Purchase> alreadyPurchases = queryAlreadyPurchasesResult.getPurchasesList();
if(alreadyPurchases!=null){
handlePurchases(alreadyPurchases);
}
}
//if purchase cancelled
else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.USER_CANCELED) {
Toast.makeText(getApplicationContext(),"Purchase Canceled",Toast.LENGTH_SHORT).show();
}
// Handle any other error msgs
else {
Toast.makeText(getApplicationContext(),"Error "+billingResult.getDebugMessage(),Toast.LENGTH_SHORT).show();
}
}
void handlePurchases(List<Purchase> purchases) {
for(Purchase purchase:purchases) {
//if item is purchased
if (PRODUCT_ID.equals(purchase.getSku()) && purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED)
{
if (!verifyValidSignature(purchase.getOriginalJson(), purchase.getSignature())) {
// Invalid purchase
// show error to user
Toast.makeText(getApplicationContext(), "Error : Invalid Purchase", Toast.LENGTH_SHORT).show();
return;
}
// else purchase is valid
//if item is purchased and not consumed
if (!purchase.isAcknowledged()) {
ConsumeParams consumeParams = ConsumeParams.newBuilder()
.setPurchaseToken(purchase.getPurchaseToken())
.build();
billingClient.consumeAsync(consumeParams, consumeListener);
}
}
//if purchase is pending
else if( PRODUCT_ID.equals(purchase.getSku()) && purchase.getPurchaseState() == Purchase.PurchaseState.PENDING)
{
Toast.makeText(getApplicationContext(),
"Purchase is Pending. Please complete Transaction", Toast.LENGTH_SHORT).show();
}
//if purchase is refunded or unknown
else if(PRODUCT_ID.equals(purchase.getSku()) && purchase.getPurchaseState() == Purchase.PurchaseState.UNSPECIFIED_STATE)
{
Toast.makeText(getApplicationContext(), "Purchase Status Unknown", Toast.LENGTH_SHORT).show();
}
}
}
ConsumeResponseListener consumeListener = new ConsumeResponseListener() {
@Override
public void onConsumeResponse(BillingResult billingResult, String purchaseToken) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
int consumeCountValue=getPurchaseCountValueFromPref()+1;
savePurchaseCountValueToPref(consumeCountValue);
Toast.makeText(getApplicationContext(), "Item Consumed", Toast.LENGTH_SHORT).show();
consumeCount.setText("Item Consumed "+getPurchaseCountValueFromPref()+" Time(s)");
}
}
};
/**
* Verifies that the purchase was signed correctly for this developer's public key.
* <p>Note: It's strongly recommended to perform such check on your backend since hackers can
* replace this method with "constant true" if they decompile/rebuild your app.
* </p>
*/
private boolean verifyValidSignature(String signedData, String signature) {
try {
//for old playconsole
// To get key go to Developer Console > Select your app > Development Tools > Services & APIs.
//for new play console
//To get key go to Developer Console > Select your app > Monetize > Monetization setup
String base64Key = "Add your key here";
return Security.verifyPurchase(base64Key, signedData, signature);
} catch (IOException e) {
return false;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if(billingClient!=null){
billingClient.endConnection();
}
}
}
Security.java
/*
* Copyright (c) 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.programtown.example;
import android.text.TextUtils;
import android.util.Base64;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
/**
* Security-related methods. For a secure implementation, all of this code should be implemented on
* a server that communicates with the application on the device.
*/
public class Security {
private static final String TAG = "IABUtil/Security";
private static final String KEY_FACTORY_ALGORITHM = "RSA";
private static final String SIGNATURE_ALGORITHM = "SHA1withRSA";
/**
* Verifies that the data was signed with the given signature, and returns the verified
* purchase.
* @param base64PublicKey the base64-encoded public key to use for verifying.
* @param signedData the signed JSON string (signed, not encrypted)
* @param signature the signature for the data, signed with the private key
* @throws IOException if encoding algorithm is not supported or key specification
* is invalid
*/
public static boolean verifyPurchase(String base64PublicKey, String signedData,
String signature) throws IOException {
if (TextUtils.isEmpty(signedData) || TextUtils.isEmpty(base64PublicKey)
|| TextUtils.isEmpty(signature)) {
//Purchase verification failed: missing data
return false;
}
PublicKey key = generatePublicKey(base64PublicKey);
return verify(key, signedData, signature);
}
/**
* Generates a PublicKey instance from a string containing the Base64-encoded public key.
*
* @param encodedPublicKey Base64-encoded public key
* @throws IOException if encoding algorithm is not supported or key specification
* is invalid
*/
public static PublicKey generatePublicKey(String encodedPublicKey) throws IOException {
try {
byte[] decodedKey = Base64.decode(encodedPublicKey, Base64.DEFAULT);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM);
return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));
} catch (NoSuchAlgorithmException e) {
// "RSA" is guaranteed to be available.
throw new RuntimeException(e);
} catch (InvalidKeySpecException e) {
String msg = "Invalid key specification: " + e;
throw new IOException(msg);
}
}
/**
* Verifies that the signature from the server matches the computed signature on the data.
* Returns true if the data is correctly signed.
*
* @param publicKey public key associated with the developer account
* @param signedData signed data from server
* @param signature server signature
* @return true if the data and signature match
*/
public static boolean verify(PublicKey publicKey, String signedData, String signature) {
byte[] signatureBytes;
try {
signatureBytes = Base64.decode(signature, Base64.DEFAULT);
} catch (IllegalArgumentException e) {
//Base64 decoding failed
return false;
}
try {
Signature signatureAlgorithm = Signature.getInstance(SIGNATURE_ALGORITHM);
signatureAlgorithm.initVerify(publicKey);
signatureAlgorithm.update(signedData.getBytes());
if (!signatureAlgorithm.verify(signatureBytes)) {
//Signature verification failed
return false;
}
return true;
} catch (NoSuchAlgorithmException e) {
// "RSA" is guaranteed to be available
throw new RuntimeException(e);
} catch (InvalidKeyException e) {
//Invalid key specification
} catch (SignatureException e) {
//Signature exception
}
return false;
}
}
6 Run and Test the application
Now Run your app and test in app purchase by using your gmail account that is associated with your Google Console developer account. Because by default, the only test account registered is the one that’s associated with your developer account. You can register additional test accounts by using the Google Play Console.






Conclusion
So in this article we have learnt how to integrate and make in app purchase of single consumable product. If you liked the article then please share this page and article. Thanks.
NOTE:
Need Support?
References:
https://developer.android.com/google/play/billing/billing_overview
Please Subscribe Youtube| Like Facebook | Follow Twitter
Hi @ProgramTown,
I want to know can you post guide upgrade purchase billing version 3 to version 4 and version 5?
Thanks!
hi
unfortunately there is no migration guide
However for latest library version 5 sample paid code you can get it from here
https://programtown.com/android-google-in-app-purchase-subscription-download-example-source-code-google-play-billing-library-5-0-java-kotlin/
Very good and simple tutorial. Thank u so a lot
thanks
Hello, Thanks to you, I added the necessary features to my application. I also coded the server side control feature. I used PHP for this. Everything works very well. But I have a problem. When I change the price of the product, there is no instant price update. Before that, the product price was updated instantly in the library that I had been using for about 4 years. How can we ensure that the prices are updated instantly when using the way you describe? I was using the “iab v3” library on github before. And after changing the price, closing the purchase screen and tapping the button again, the current price was instantly displayed. I hope I was able to explain my problem. Waiting for your reply. Thanks
please consult google support for this
Thanks man, article helped a lot
thanks
Thank you very much, I did your tutorial and it worked.
Welcome
Please share this article and website
Thanks
I’m using billing:4.0.0 and cannot resolve method getSku() in Purchase. Any ideas? Thanks!
It is due to Library version 4.
change purchase.getSkus().contains(PRODUCT_ID)
I have not done yet for version 4.
code It is working fine for version 3
However for version 4 you can book order for personal support and implementation with latest library via Fiver.
https://www.fiverr.com/share/NEW6eN
Thanks
That’s a really great post and helped me a lot.
Thank you very much
Welcome dear user
Please share this article and website
Thanks