How to implement In-App Billing in an Android application? How to implement In-App Billing in an Android application? android android

How to implement In-App Billing in an Android application?


Well, I'll try to explain what I experienced. I don't consider myself an expert on this but I broke my head several days.

For starters, I had a very bad time trying to understand the workflow of the example and the application. I thought it should be better to start with a simple example however its much difficult to separate the code in small pieces and not knowing if you are breaking anything. I'll tell you what I have and what I changed from the example to make it work.

I have a single Activity where all my purchases come from. It's called Pro.

First, you should update the variable base64EncodedPublicKey in your Security class with your public Market developer key or you will see a nice Exception.

Well, I bind my Activity to my BillingService like so:

      public class Pro extends TrackedActivity implements OnItemClickListener {            private BillingService mBillingService;            private BillingPurchaseObserver mBillingPurchaseObserver;            private Handler mHandler;            @Override            protected void onCreate(Bundle savedInstanceState) {                    super.onCreate(savedInstanceState);                     setContentView(R.layout.pro);                //Do my stuff                mBillingService = new BillingService();                mBillingService.setContext(getApplicationContext());                mHandler = new Handler();                mBillingPurchaseObserver = new BillingPurchaseObserver(mHandler);            }        }    @Override    protected void onStart() {       //Register the observer to the service        super.onStart();        ResponseHandler.register(mBillingPurchaseObserver);       }    @Override    protected void onStop() {        //Unregister the observer since you dont need anymore        super.onStop();        ResponseHandler.unregister(mBillingPurchaseObserver);    }    @Override    protected void onDestroy() {       //Unbind the service        super.onDestroy();        mBillingService.unbind();    }

That way, all the purchases talk to this service, that will then, send the JSON requests to the market. You might think that the purchases are made on the same instant but no. You send the request and the purchase might come minutes or hours later. I think this is mainly to server overload and approval of the credit cards.

Then I have a ListView with my items, and I open a AlertDialog on each one, inviting them to buy the item. When they click on an item, I do this:

  private class BuyButton implements DialogInterface.OnClickListener {       private BillingItem item = null;       private String developerPayload;       public BuyButton(BillingItem item, String developerPayload) {        this.item = item;        this.developerPayload = developerPayload;        }            @Override            public void onClick(DialogInterface dialog, int which) {                if (GeneralHelper.isOnline(getApplicationContext())){                    //I track the buy here with GA SDK.         mBillingService.requestPurchase(this.item.getSku(), this.developerPayload);                             } else {                                    Toast.makeText(getApplicationContext(), R.string.msg_not_online, Toast.LENGTH_SHORT).show();                }            }        }

Alright, you should see that the Market opens and the user either finishes or cancels the buy.

Whats then important is my PurChaseObserver, which handles all the events that market sends. This is a stripped version of it but you should get the point (See my comments throught the code):

private class BillingPurchaseObserver extends PurchaseObserver {        public BillingPurchaseObserver(Handler handler) {            super(Pro.this, handler);        }        @Override        public void onBillingSupported(boolean supported) {            if (supported) {                //Enable buy functions. Not required, but you can do stuff here. The market first checks if billing is supported. Maybe your country is not supported, for example.             } else {                Toast.makeText(getApplicationContext(), R.string.billing_not_supported, Toast.LENGTH_LONG).show();            }        }        @Override        public void onPurchaseStateChange(PurchaseState purchaseState, String itemId,                int quantity, long purchaseTime, String developerPayload) {//This is the method that is called when the buy is completed or refunded I believe. // Here you can do something with the developerPayload. Its basically a Tag you can use to follow your transactions. i dont use it.         BillingItem item = BillingItem.getBySku(getApplicationContext(), itemId);        if (purchaseState == PurchaseState.PURCHASED) {            if (item != null){//This is my own implementation that sets the item purchased in my database. BillingHelper is a class with methods I use to check if the user bought an option and update the UI. You should also check for refunded. You can see the Consts class to find what you need to check for.                     boolean resu = item.makePurchased(getApplicationContext());                    if (resu){                                              Toast.makeText(getApplicationContext(), R.string.billing_item_purchased, Toast.LENGTH_LONG).show();                    }                }            }        }        private void trackPurchase(BillingItem item, long purchaseTime) {                       //My code to track the purchase in GA        }        @Override        public void onRequestPurchaseResponse(RequestPurchase request,                ResponseCode responseCode) {               //This is the callback that happens when you sent the request. It doesnt mean you bought something. Just that the Market received it.             if (responseCode == ResponseCode.RESULT_OK) {                               Toast.makeText(getApplicationContext(), R.string.billing_item_request_sent, Toast.LENGTH_SHORT).show();            } else if (responseCode == ResponseCode.RESULT_USER_CANCELED) {                //The user canceled the item.             } else {            //If it got here, the Market had an unexpected problem.             }        }        @Override        public void onRestoreTransactionsResponse(RestoreTransactions request,                ResponseCode responseCode) {            if (responseCode == ResponseCode.RESULT_OK) {//Restore transactions should only be run once in the lifecycle of your application unless you reinstalled the app or wipe the data.                 SharedPreferences.Editor edit = PreferencesHelper.getInstance().getDefaultSettings(getApplicationContext()).edit();                edit.putBoolean(Consts.DB_INITIALIZED, true);                edit.commit();            } else {    //Something went wrong            }        }    }

And I believe you shouldn't need to edit anything else. The rest of the code "works". You can try using the sample SKU at first in your own items "android.test.purchased". So far I have tested this and it works however I still need to cover everything like the refunded state. In this case, I am letting the user keep the features but I want to make sure it works perfect before modyfing it.

I hope it helps you and others.


V3: here is an tutorial for a quick start.. He´s using the helper-classes from the google example (Trivial Drive) ... Good as first "Hello Billing" ..

http://www.techotopia.com/index.php/Integrating_Google_Play_In-app_Billing_into_an_Android_Application_%E2%80%93_A_Tutorial


There is a full example of Android In-App Billing v3 step by step is given here with screenshot. Please check the tutorial: Android In-App Billing v3 using ServiceConnection Class

Hope it will help.

For more clarification, go through this tutorial: Implementing In-app Billing in Version 3 API

Steps to follow to Integrate In-app Billing library in our project

Update your AndroidManifest.xml file.

Create a ServiceConnection and bind it to IInAppBillingService.

Send In-app Billing requests from your application to IInAppBillingService.

Handle In-app Billing responses from Google Play.

Update AndroidManifest.xml

<uses-permission android:name="com.android.vending.BILLING" />

Add the permissions in Manifest.xml file

Adding the AIDL file to your project

Build your application. You should see a generated file named IInAppBillingService.java in the /gen directory of your project.

Update Dependencies in build.gradle file

apply plugin: 'com.android.application'android {    compileSdkVersion 24    buildToolsVersion "24.0.0"    defaultConfig {        applicationId "com.inducesmile.androidinapppurchase"        minSdkVersion 14        targetSdkVersion 24        versionCode 2        versionName "1.1"    }    buildTypes {        release {            minifyEnabled false            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'        }    }}dependencies {    compile fileTree(dir: 'libs', include: ['*.jar'])    testCompile 'junit:junit:4.12'    compile 'com.android.support:appcompat-v7:24.1.1'    compile 'com.intuit.sdp:sdp-android:1.0.3'    compile 'com.android.support:support-annotations:24.1.1'    compile 'org.jetbrains:annotations-java5:15.0'}

InAppPurchaseActivity.java and activity_in_app_purchase.xml

This is where will offer our app users the opportunity to to make in-app purchase. In the layout file, we will give the user the opportunity to make purchase in different denominations.

InAppPurchaseActivity.java

Note: getAllUserPurchase() and itemPurchaseAvailability() methods should be called in non UI Thread to avoid app crashing.

public class InAppPurchaseActivity extends AppCompatActivity {    private static final String TAG = InAppPurchaseActivity.class.getSimpleName();    private IInAppBillingService mService;    private CustomSharedPreference customSharedPreference;    String[] productIds = new String[]{Helper.ITEM_ONE_ID, Helper.ITEM_TWO_ID, Helper.ITEM_THREE_ID};    private ImageView buyOneButton, buyTwoButton, buyThreeButton;    private static final char[] symbols = new char[36];    static {        for (int idx = 0; idx < 10; ++idx)            symbols[idx] = (char) ('0' + idx);        for (int idx = 10; idx < 36; ++idx)            symbols[idx] = (char) ('a' + idx - 10);    }    private String appPackageName;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_in_app_purchase);        appPackageName = this.getPackageName();        Intent serviceIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND");        serviceIntent.setPackage("com.android.vending");        bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE);        customSharedPreference = new CustomSharedPreference(InAppPurchaseActivity.this);        buyOneButton = (ImageView)findViewById(R.id.buy_one);        buyOneButton.setVisibility(View.GONE);        assert buyOneButton != null;        buyOneButton.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View view) {                if(!isBillingSupported()){                    Helper.displayMessage(InAppPurchaseActivity.this, getString(R.string.in_app_support));                    return;                }                purchaseItem(Helper.ITEM_ONE_ID);            }        });        buyTwoButton = (ImageView)findViewById(R.id.buy_two);        buyTwoButton.setVisibility(View.GONE);        assert buyTwoButton != null;        buyTwoButton.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View view) {                if(!isBillingSupported()){                    Helper.displayMessage(InAppPurchaseActivity.this, getString(R.string.in_app_support));                    return;                }                purchaseItem(Helper.ITEM_TWO_ID);            }        });        buyThreeButton = (ImageView)findViewById(R.id.buy_three);        buyThreeButton.setVisibility(View.GONE);        assert buyThreeButton != null;        buyThreeButton.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View view) {                if(!isBillingSupported()){                    Helper.displayMessage(InAppPurchaseActivity.this, getString(R.string.in_app_support));                    return;                }                purchaseItem(Helper.ITEM_THREE_ID);            }        });    }    ServiceConnection mServiceConn = new ServiceConnection() {        @Override        public void onServiceDisconnected(ComponentName name) {            mService = null;        }        @Override        public void onServiceConnected(ComponentName name, IBinder service) {            mService = IInAppBillingService.Stub.asInterface(service);            AvailablePurchaseAsyncTask mAsyncTask = new AvailablePurchaseAsyncTask(appPackageName);            mAsyncTask.execute();        }    };    private void purchaseItem(String sku){        String generatedPayload = getPayLoad();        customSharedPreference.setDeveloperPayLoad(generatedPayload);        try {            Bundle buyIntentBundle = mService.getBuyIntent(3, getPackageName(), sku, "inapp", generatedPayload);            PendingIntent pendingIntent = buyIntentBundle.getParcelable("BUY_INTENT");            try {                startIntentSenderForResult(pendingIntent.getIntentSender(), Helper.RESPONSE_CODE, new Intent(), Integer.valueOf(0), Integer.valueOf(0), Integer.valueOf(0));            } catch (IntentSender.SendIntentException e) {                e.printStackTrace();            }        } catch (RemoteException e) {            e.printStackTrace();        }    }    @Override    protected void onActivityResult(int requestCode, int resultCode, Intent data) {        if (requestCode == Helper.RESPONSE_CODE) {            int responseCode = data.getIntExtra("RESPONSE_CODE", 0);            String purchaseData = data.getStringExtra("INAPP_PURCHASE_DATA");            String dataSignature = data.getStringExtra("INAPP_DATA_SIGNATURE");            if (resultCode == RESULT_OK) {                try {                    JSONObject purchaseJsonObject = new JSONObject(purchaseData);                    String sku = purchaseJsonObject.getString("productId");                    String developerPayload = purchaseJsonObject.getString("developerPayload");                    String purchaseToken = purchaseJsonObject.getString("purchaseToken");                    //the developerPayload value is better stored in remote database but in this tutorial                    //we will use a shared preference                    for(int i = 0; i < productIds.length; i++){                        if(productIds[i].equals(sku) && developerPayload.equals(customSharedPreference.getDeveloperPayload())){                            customSharedPreference.setPurchaseToken(purchaseToken);                            //access to private content                            Intent contentIntent = new Intent(InAppPurchaseActivity.this, PrivateContentActivity.class);                            startActivity(contentIntent);                        }                    }                }                catch (JSONException e) {                    e.printStackTrace();                }            }        }    }    private String getPayLoad(){        RandomString randomString = new RandomString(36);        String payload = randomString.nextString();        return payload;    }    public class RandomString {        private final Random random = new Random();        private final char[] buf;        public RandomString(int length) {            if (length < 1)                throw new IllegalArgumentException("length < 1: " + length);            buf = new char[length];        }        public String nextString() {            for (int idx = 0; idx < buf.length; ++idx)                buf[idx] = symbols[random.nextInt(symbols.length)];            return new String(buf);        }    }    public final class SessionIdentifierGenerator {        private SecureRandom random = new SecureRandom();        public String nextSessionId() {            return new BigInteger(130, random).toString(32);        }    }    private class AvailablePurchaseAsyncTask extends AsyncTask<Void, Void, Bundle> {        String packageName;        public AvailablePurchaseAsyncTask(String packageName){            this.packageName = packageName;        }        @Override        protected Bundle doInBackground(Void... voids) {            ArrayList<String> skuList = new ArrayList<String>();            skuList.add(Helper.ITEM_ONE_ID);            skuList.add(Helper.ITEM_TWO_ID);            skuList.add(Helper.ITEM_THREE_ID);            Bundle query = new Bundle();            query.putStringArrayList(Helper.ITEM_ID_LIST, skuList);            Bundle skuDetails = null;            try {                skuDetails = mService.getSkuDetails(3, packageName, "inapp", query);            } catch (RemoteException e) {                e.printStackTrace();            }            return skuDetails;        }        @Override        protected void onPostExecute(Bundle skuDetails) {            List<AvailablePurchase> canPurchase = new ArrayList<AvailablePurchase>();            int response = skuDetails.getInt("RESPONSE_CODE");            if (response == 0) {                ArrayList<String> responseList = skuDetails.getStringArrayList("DETAILS_LIST");                if(responseList != null){                    for (String thisResponse : responseList) {                        JSONObject object = null;                        try {                            object = new JSONObject(thisResponse);                            String sku = object.getString("productId");                            String price = object.getString("price");                            canPurchase.add(new AvailablePurchase(sku, price));                        } catch (JSONException e) {                            e.printStackTrace();                        }                    }                }            }            if(checkIfPurchaseIsAvailable(canPurchase, productIds[0])){                buyOneButton.setVisibility(View.VISIBLE);            }else{                buyOneButton.setVisibility(View.GONE);            }            if(checkIfPurchaseIsAvailable(canPurchase, productIds[1])){                buyTwoButton.setVisibility(View.VISIBLE);            }else{                buyTwoButton.setVisibility(View.GONE);            }            if(checkIfPurchaseIsAvailable(canPurchase, productIds[2])){                buyThreeButton.setVisibility(View.VISIBLE);            }else{                buyThreeButton.setVisibility(View.GONE);            }        }    }    @org.jetbrains.annotations.Contract("null, _ -> false")    private boolean checkIfPurchaseIsAvailable(List<AvailablePurchase> all, String productId){        if(all == null){ return false;}        for(int i = 0; i < all.size(); i++){            if(all.get(i).getSku().equals(productId)){                return true;            }        }        return false;    }    public boolean isBillingSupported(){        int response = 1;        try {            response = mService.isBillingSupported(3, getPackageName(), "inapp");        } catch (RemoteException e) {            e.printStackTrace();        }        if(response > 0){            return false;        }        return true;    }    public void consumePurchaseItem(String purchaseToken){        try {            int response = mService.consumePurchase(3, getPackageName(), purchaseToken);            if(response != 0){                return;            }        } catch (RemoteException e) {            e.printStackTrace();        }    }    public Bundle getAllUserPurchase(){        Bundle ownedItems = null;        try {            ownedItems = mService.getPurchases(3, getPackageName(), "inapp", null);        } catch (RemoteException e) {            e.printStackTrace();        }        return ownedItems;    }    public List<UserPurchaseItems> extractAllUserPurchase(Bundle ownedItems){        List<UserPurchaseItems> mUserItems = new ArrayList<UserPurchaseItems>();        int response = ownedItems.getInt("RESPONSE_CODE");        if (response == 0) {            ArrayList<String> ownedSkus = ownedItems.getStringArrayList("INAPP_PURCHASE_ITEM_LIST");            ArrayList<String>  purchaseDataList = ownedItems.getStringArrayList("INAPP_PURCHASE_DATA_LIST");            ArrayList<String>  signatureList = ownedItems.getStringArrayList("INAPP_DATA_SIGNATURE_LIST");            String continuationToken = ownedItems.getString("INAPP_CONTINUATION_TOKEN");            if(purchaseDataList != null){                for (int i = 0; i < purchaseDataList.size(); ++i) {                    String purchaseData = purchaseDataList.get(i);                    assert signatureList != null;                    String signature = signatureList.get(i);                    assert ownedSkus != null;                    String sku = ownedSkus.get(i);                    UserPurchaseItems allItems = new UserPurchaseItems(sku, purchaseData, signature);                    mUserItems.add(allItems);                }            }        }        return mUserItems;    }    @Override    public void onDestroy() {        super.onDestroy();        if (mService != null) {            unbindService(mServiceConn);        }    }}

Create Helper Package Directory

Create a new package folder and name it helpers. Inside the package, create a new java file Helper.java.

Helper.java

public class Helper {    public static final String ITEM_ID_LIST = "ITEM_ID_LIST";    public static final String ITEM_ONE_ID = "productone";    public static final String ITEM_TWO_ID = "producttwo";    public static final String ITEM_THREE_ID = "productthree";    public static final int RESPONSE_CODE = 1001;    public static final String SHARED_PREF = "shared_pref";    public static final String DEVELOPER_PAYLOAD = "developer_payload";    public static final String PURCHASE_TOKEN = "purchase_token";    public static void displayMessage(Context context, String message){        Toast.makeText(context.getApplicationContext(), message, Toast.LENGTH_LONG).show();    }}

Testing In-App Billing Purchase

  1. Create a Google+ account(don't use main account)
  2. Add the users that will test the app in your group or community.

Errors You might encounter during In-App purchase testing

the item you requested is not available for purchase

Solution – According to AndreiBogdan in Stackoverflow,

All credit goes to Inducesmile for his tutorial

Android Developer Blog also recommends a training class on Selling In-app Products. To see a complete implementation and learn how to test the application, Please check this tutorial: Selling In-app Products