📜 ⬆️ ⬇️

Experience of integrating the OpenIAB library into the Android application

In this article, we would like to share our experience of integrating the OpenIAB library into our Android application that helps you learn English words . If anyone does not know, then OpenIAB is a library that allows you to connect in-App purchases of various app stores, abstracting from the details of the implementation of the API of a particular store.


OpenIAB is developed on the basis of the following principles:

* The library API should be as similar as possible to the Google Play In-app Billing API.
* One APK file should work for all supported app stores.
* No intermediaries when making payments. This means that there are no third parties that process transactions. Under the hood of the library, all transactions are processed by the same Google Play, Yandex.Store and other native store applications. In fact, OpenIAB is a layer that brings the various appstor APIs to the same API, which we will use in our application.

Connection process


Connect the OpenIAB library to the project


To use the library's API, we, of course, need to connect it to our project. This can be done in several ways:
1. Download the jar library file from here (http://www.onepf.org/openiab) and put it in the lib folder of our eclipse project.
2. Clone the project from the git repository (git clone github.com/onepf/OpenIAB.git ) and connect it to the project as the Library Project.
The first option is simpler, and we will use it.
')
In order for the library to interact with the app stores, it will need an internet connection, so do not forget to add the permission <uses-permission android: name = "android.permission.INTERNET" /> in AndroidManifest.xml.

<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" ... <!-- permissions to download files and billing support --> <uses-permission android:name="android.permission.INTERNET" /> ... </manifest> 


This completes the connection of the library to the project, and it is time to configure it to work with the application store.

Customization


Our application is distributed through Yandex.Store, and we will consider setting up the library on the example of this particular store. First you need to create what we want to sell. To do this, follow the Developer Console (https://developer.store.yandex.ru) of Yandex and create a new application there. Further, in the “Purchases in the application” section, you need to create an entry for each position of the virtual goods that we will offer to the user. When adding each purchase, among other parameters, we are required to specify its ID. These IDs we will use when interacting with the app store.

Now, when purchases exist on the side of the store, it's time to prepare our application to work with Yandex.Store. The clearest thing is to immediately show the code.

 public class DictActivity extends Activity { //    API . private OpenIabHelper mIABHelper; private boolean isIABHelperSetup = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_dict); //  API. OpenIabHelper.Options.Builder builder = new OpenIabHelper.Options.Builder() .setCheckInventory(true) .addPreferredStoreName(OpenIabHelper.NAME_YANDEX) .setVerifyMode(OpenIabHelper.Options.VERIFY_EVERYTHING) .addStoreKeys(SettingsManager.STORE_KEYS_MAP); //  . mIABHelper = new OpenIabHelper(this, builder.build()); mIABHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() { public void onIabSetupFinished(IabResult result) { if(!result.isSuccess()) { isIABHelperSetup = false; //TODO:      . return; } if(mIABHelper != null) { isIABHelperSetup = true; //API   . } } }); } @Override public void onDestroy() { super.onDestroy(); if(mIABHelper != null) mIABHelper.dispose(); mIABHelper = null; } } 


mIABHelper is the very object through which the library API will be accessed. Its configuration is done by passing it to the constructor of the OpenIabHelper.Options structure. Let's look at the most useful settings possible:
- setCheckInventory (boolean checkInventory). Tells the OpenIAB library to connect to each available app store, retrieve a list of available purchases from the user, and determine whether the store is suitable for further work or not based on the data obtained.
- addPreferredStoreName (String ... storeNames). Sets the priority for using available stores. It can be useful if the purchase can be carried out through several stores simultaneously.
- addStoreKeys (Map <String, String> storeKeys). Many application stores, including Yandex.Store, sign messages sent to the application. Thus, by checking the signature, the application can always make sure that the message is not fake. To verify the signature, the public key is used, which, as a rule, can be obtained from the developer console of a specific application store. With this option, you can set the public keys for the stores used.
-setVerifyMode (int verifyMode). Defines the signature verification mode when analyzing store messages. There are three modes available:
* OpenIabHelper.Options.VERIFY_EVERYTHING - always verify the signature. If the store does not specify a public key, then working with it will not be possible.
* OpenIabHelper.Options.VERIFY_SKIP - do not verify the signature. It can be useful if you are processing purchases on the server, and not in the application.
* OpenIabHelper.Options.VERIFY_ONLY_KNOWN - check the signature only if the public key is specified for this store, for example, by the option addStoreKeys (Map <String, String> storeKeys).

The OpenIabHelper.startSetup method (IabHelper.OnIabSetupFinishedListener) starts the library initialization process. This is an asynchronous call, and the result of the operation will be returned to the onIabSetupFinished () method of the passed object.

In order for our application to use Yandex.Store for making purchases, we need to give it the appropriate permissions in the project manifest file.
 <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" ... <!-- permissions to download files and billing support --> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="org.onepf.openiab.permission.BILLING" /> ... </manifest> 


The frame is ready, we are almost ready to sell something.

Processing purchases


In order to start the purchase process, we need to make a call.
OpenIabHelper.launchPurchaseFlow (Activity act, String sku, int requestCode, IabHelper.OnIabPurchaseFinishedListener listener).
 ... if((mIABHelper != null) && isIABHelperSetup) { String sku = "duct_1"; mIABHelper.launchPurchaseFlow(DictActivity.this, sku, 10001, mPurchaseFinishedListener); } ... 


Here
act - activation from which the purchase is made.
sku is the same ID that we specified when registering a purchase in the app store.
requestCode - code to call the store activation.
listener - the listener in which we will process the result of the operation.

By itself, the OpenIAB library does not make any requests, but only delegates calls to the application of the selected store. In our case, this application will be Yandex.Store. Thus, after calling the launchPurchaseFlow () user will be redirected to the application of the corresponding store, which will conduct the purchase transaction. After the user confirms or does not confirm the purchase, we will get the result in the onActivityResult () method of our activation. In order for the OpenIAB library to interpret the response received, we must pass on this response to it. This is done by calling the handleActivityResult (int requestCode, int resultCode, Intent data). This method interprets the data received from the store application, checks the signature and transfers control to the onIabPurchaseFinished (IabResult result, Purchase Purchase) method specified when the listener startedPurchaseFlow ().

 public class DictActivity extends Activity { ... private void makePurchase() { if((mIABHelper != null) && isIABHelperSetup) { String sku = "duct_1"; mIABHelper.launchPurchaseFlow(DictActivity.this, sku, 10001, mPurchaseFinishedListener); } } ... @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if((mIABHelper == null) || !mIABHelper.handleActivityResult(requestCode, resultCode, data)) super.onActivityResult(requestCode, resultCode, data); } ... private OnIabPurchaseFinishedListener mPurchaseFinishedListener = new OnIabPurchaseFinishedListener() { public void onIabPurchaseFinished(IabResult result, Purchase purchase) { if(result.isFailure()) { //TODO: -   ,    . return; } //   String purchaseName = purchase.getSku(); markPurchaseAsConfirmed(purchaseName); } }; private void markPurchaseAsConfirmed(String purchase) { //TODO:           //  UI . } } 


Now the user has the opportunity to become the happy owner of our virtual goods, and it's time to bring the final touch to our application, namely ...

Recovery of purchases


Typically, app stores support several types of products. The most common types are: consumable goods, non-consumable goods and subscriptions. The consumed goods include, for example, in-game currency. The user can purchase the consumed product several times. An example of a non-consumable product is any additional functionality of your application. User pays it once and always uses.

In our case, we allow the user to purchase additional dictionaries, which, logically, should not be consumed goods. The feature of non-consumable goods is that they are tied to a user account, and not to a specific instance of the program. This means that even if a user has installed your application on several devices under one account, he can only purchase goods once, and the purchased goods must be available on all these devices. Therefore, we should be able to receive information from the application store on goods that have been purchased once, so that the user can use them in the newly installed application. In addition, the process of processing a purchase transaction is quite complex and involves the transfer of data over the network and the interaction of several applications within the system itself. At any moment something might go wrong. In the worst case, a failure can occur after the purchase was made from the app store’s point of view, but before we processed the purchase in our application. In this case, the user will not receive the desired product in the application, although it will be listed as purchased in the app store. To handle these situations, call
queryInventoryAsync (IabHelper.QueryInventoryFinishedListener listener).

This method will make an asynchronous request to the application store for the presence of goods purchased by the user and will return an answer to the listener. A good solution would be to call this method immediately after the OpenIAB library was initialized.

 public class DictActivity extends Activity { ... @Override public void onIabSetupFinished(IabResult result) { if(!result.isSuccess()) { isIABHelperSetup = false; //TODO:      . return; } if(mIABHelper != null) { isIABHelperSetup = true; //API   . //    . mIABHelper.queryInventoryAsync(mGotInventoryListener); } } ... private QueryInventoryFinishedListener mGotInventoryListener = new QueryInventoryFinishedListener() { @Override public void onQueryInventoryFinished(IabResult result, Inventory inventory) { if (result.isFailure()) { //TODO:   . return; } //        . List<Purchase> purchases = inventory.getAllPurchases(); for(Purchase p : purchases) { String sku = p.getSku(); if(!isPurchaseConfirmed(sku)) markPurchaseAsConfirmed(sku); } } }; private boolean isPurchaseConfirmed(String purchase) { //TODO:  true,      , //  false. } private void markPurchaseAsConfirmed(String purchase) { //TODO:    ,   ,    //  UI . } } 


Together


 public class DictActivity extends BaseActivity { //    API . private OpenIabHelper mIABHelper; private boolean isIABHelperSetup = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_dict); //  API. OpenIabHelper.Options.Builder builder = new OpenIabHelper.Options.Builder() .setCheckInventory(true) .addPreferredStoreName(OpenIabHelper.NAME_YANDEX) .setVerifyMode(OpenIabHelper.Options.VERIFY_EVERYTHING) .addStoreKeys(SettingsManager.STORE_KEYS_MAP); //  . mIABHelper = new OpenIabHelper(this, builder.build()); mIABHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() { public void onIabSetupFinished(IabResult result) { if(!result.isSuccess()) { isIABHelperSetup = false; //TODO:      . return; } if(mIABHelper != null) { isIABHelperSetup = true; //API   . //    . mIABHelper.queryInventoryAsync(mGotInventoryListener); } } }); final Button buy = (Button)findViewById(R.id.buy); buy.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if((mIABHelper != null) && isIABHelperSetup) { String payload = "payload"; String sku = "duct_1"; mIABHelper.launchPurchaseFlow(DictActivity.this, sku, 10001, mPurchaseFinishedListener, payload); } } }); } @Override public void onDestroy() { super.onDestroy(); if(mIABHelper != null) mIABHelper.dispose(); mIABHelper = null; } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if((mIABHelper == null) || !mIABHelper.handleActivityResult(requestCode, resultCode, data)) super.onActivityResult(requestCode, resultCode, data); } private OnIabPurchaseFinishedListener mPurchaseFinishedListener = new OnIabPurchaseFinishedListener() { public void onIabPurchaseFinished(IabResult result, Purchase purchase) { if (result.isFailure()) { showMessage("Error purchasing: " + result); return; } String purchaseName = purchase.getSku(); markPurchaseAsConfirmed(purchaseName); } }; private QueryInventoryFinishedListener mGotInventoryListener = new QueryInventoryFinishedListener() { @Override public void onQueryInventoryFinished(IabResult result, Inventory inventory) { if (result.isFailure()) { //TODO:   . return; } //        . List<Purchase> purchases = inventory.getAllPurchases(); for(Purchase p : purchases) { String sku = p.getSku(); if(!isPurchaseConfirmed(sku)) markPurchaseAsConfirmed(sku); } } }; private void markPurchaseAsConfirmed(String purchase) { //TODO:    ,   ,    //  UI . } private boolean isPurchaseConfirmed(String purchase) { //TODO:  true,      , //  false. } } 


Conclusion


If you plan to make money on your application outside the google play infrastructure, then OpenIAB is what you need. Connecting new app stores is carried out with just a few lines of code, and the similarity of the API with the Google Billing API allows you to quickly migrate to it from Google Play.

Links


github.com/onepf/OpenIAB is a project on GitHub.
www.engwords.net/ru - for example, an application site with built-in OpenIAB for Yandex.Store

Source: https://habr.com/ru/post/238531/


All Articles