Implementing Myket In-App Purchase

You are here:

Modifications made to TrivialDrive

In order to help you in the implementation of in-app purchases, we have prepared a sample app which shows you how to use the in-app purchases in a game. This app, which is known as TrivialDrive, is used by Google Play to simulate in-app purchases. We made two small modifications in the source code of this app to connect to Myket in-app purchase instead of the Google Play service. Therefore, the previously available source codes which were intended to work with Google Play in-app purchase service can be quickly modified for compatibility with Myket. In fact, that is why the API of Myket in-app purchase service was implemented similar to that of Google Play. The modifications applied to the original app are as follows:

Change 1: Remove the following line from the file AndroidManifest.xml:

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

And replace it with the following code:

<uses-permission android:name="ir.mservices.market.BILLING" />

Change 2: Remove the two following lines from the class IabHelper:

Intent serviceIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND");
 
serviceIntent.setPackage("com.android.vending");

And replace them with the two following lines:

Intent serviceIntent = new Intent("ir.mservices.market.InAppBillingService.BIND");
 
serviceIntent.setPackage("ir.mservices.market");

These modifications have already been made to the project provided to you. The TrivialDrive game app shows you how to use the in-app purchase API to buy and consume the products in a driving game. Moreover, it includes classes which further facilitate the in-app purchase operations, e.g. automatically confirming the data signatures.

Obfuscate your code before release. To do so, see the ProGuard tool provided in the Android SDK. Refer to end of this document.

Introduction to In-App Purchase Models

Myket offers three different models for making money using in-app purchases: the consumable, non-consumable, and subscription products. In the consumable model, the user has to make a new purchase after consuming the product, which can be coins, gems, etc. In the non-consumable model, users make a one-time payment for products and services such as unlocking game levels, removing advertisements and buying a premium account. Subscription products are offered to the user for a limited period, examples of which are the one-month subscription of a music app or one-year subscription of a digital e-book reader. In this model, the permitted period to use the app is controlled by Myket, freeing the app from involvement in the process. The implementation procedure for these three models is presented in the following.

Downloading helper files

Download and add the helper files to your app. To do this, download TrivialDrive from here (or in the Github) and follow the instruction below:

‌1. Transfer the “util” directory to your app.

2. Copy the file IInAppBillingService.aidl to the directory at src/main/aidl/com.android.vending.billing. The structure of your project should  be carrying out these two steps:

Note that not all android projects have the same structure. In the case of projects created by Android Studio, the AIDL files should be copied into the directory “java/aidl”. However, in older structures such as those created by Eclipse, the AIDL files should be copied in their respective directory addresses beside other Java files:
src/com/android/vending/licensing

3. Add the following access permission to the AndroidManifest.xml of your app

<uses-permission android:name="ir.mservices.market.BILLING" />

4. Finally, your project can be built with no errors.

Implementation stages

Creating IabHelper

First, you need to create an Instance of IabHelper. You’ll need access to this object in different methods and Callbacks; therefore, it is better to define this Instance as a field in your Activity. In order to create IabHelper, you are required to pass the public key you received from the Developers Panel along with Context to the constructor of the IabHelper class:

mHelper = new IabHelper(context, base64EncodedPublicKey);

Configuring the Logger


The IabHelper class prints its logs with the “IabHelper” tag to the android outputs logs (LogCat). You may disable or enable these logs:

mHelper.enableDebugLogging(true);

Enable the logs in the to debug mode and remember to disable them prior to release. You may use the Gradle settings for this purpose.


Connecting to Myket service (startSetup)

Use the startSetup method to connect to the Myket in-app purchase service. This method should be called before any other methods, and once connected, you may use the service. Then, implement a Callback of type OnIabSetupFinishedListener in the parameter of this method:

mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
public void onIabSetupFinished(IabResult result) {
 
}
});
}

After attempting to connect to the service, the on IabSetupFinished is called along with an IabResult to report the result. In case the isSucess method in the IabResult returns TRUE, the connection has succeeded. Otherwise, your app has failed to connect to the Myket service, in which case you may use the getMessage method to further investigate the error.

All callbacks from the IabHelper involve delays in their responses. Remember to check if the instances of this object are Null:

if (mHelper == null) return;

Updating purchase information (queryInventory)

You must update the user purchases once connected to the Myket service, i.e. when the method isSuccess in the IabResult returns TRUE. Updating user purchases are associated with different concepts in different product models:

In the case of consumable products, it means that an unconsumed purchase is available, which must be consumed and delivered to the user. For example, assume the user encounters an error during a purchase process or your app closes or is disconnected due to any reason before consuming a product. In this case, you must consume this product and deliver it to the user in the next run of your app.

The non-consumable products should also be updated so that you may know whether the user has purchased your product. If so, you may activate the considered option, e.g. removal of advertisements or unlocking all levels for the user.

In the case of subscription products, this process allows you to check whether the user is still subscribed to your app. You will be notified in the queryInventory in case the user subscription has not expired yet.

In fact, the queryInventory always returns all unconsumed products, which should be consumed and delivered to the customer if consumable, or just delivered if non-consumable or subscriptive.

Moreover, you can use the queryInventory to retrieve the product information you previously entered in the developer panel, e.g. product name, description, price, etc. For instance, in case the name or price of a product changes, you can use this capability in the UI of your app to always display the updated information stored in the Myket Developers Panel.

In order to update the purchases, the product names along with a callback of type QueryInventoryFinishedListener should be passed to the method queryInventoryAsync from the IabHelper class:

try { mHelper.queryInventoryAsync(true, itemSkus, subSkus, mGotInventoryListener); } catch (IabAsyncInProgressException e) { Log.e(TAG, "Error querying inventory. Another async operation in progress."); }

The method queryInventoryAsync accepts the following four parameters, respectively:

boolean querySkuDetails: Set the value of this TRUE in case you want the product information such as name and price to be updated. Note that if you are not planning to use this capability, set this parameter to FALSE so that no additional services are called.

List<String> moreItemSkus: A list of IDs of consumable and non-consumable products available for purchase. Set this list to NULL if your app has no purchasable products.

List<String> moreSubsSkus: A list of IDs of subscription products. In case you have no subscription products, set this list to NULL.

QueryInventoryFinishedListener listener: Set a callback of type QueryInventoryFinishedListener for the response of this service.

Note that only one request can be sent to the Myket service at a time. The IabAsyncInProgressException error occurs in case you send another request while a previous one is still in progress.

The response to this request is returned by the method onQueryInventoryFinished, which contains two objects named IabResult and Inventory. The former determines whether the request was successful, and the latter holds the updated purchases and product information. Inventory includes two Maps named mSkuMap and mPurchaseMap. The updated purchases are stored in mPurchaseMap, where you may use the method getPurchase from the class Inventory to find out whether an updated purchase is available for your considered product. In addition, you may use the method getAllPurchases to access the entire list of items, which are of Purchase type.

The SkuMap also holds the purchase information set in the Developers Panel. Note that this Map assumes a NULL value in case the querySkuDetails is set to FALSE. You may use the method getSkuDetails in the class Inventory to retrieve the product purchase information and update the UI of your app.

The queryInventory integrates the concepts behinds getPurchases and get SkuDetails. In fact, by each queryInventory, the method getPurchases is called, along with which the getSkuDetail is also called if the querySkuDetails is set to TRUE. In order to get a better grasp of these APIs, please watch our introductory tutorial videos or read the documents on introduction to Myket in-app purchases.

 An example of QueryInventoryFinishedListener is shown here:

IabHelper.QueryInventoryFinishedListener mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener() {
public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
// Have we been disposed of in the meantime? If so, quit.
if (mHelper == null) return;
// Is it a failure?
if (result.isFailure()) {
complain("Failed to query inventory: " + result);
return;
}
// (1)
// Do we have the premium upgrade?
Purchase premiumPurchase = inventory.getPurchase(SKU_PREMIUM);
Log.d(TAG, "User is " + (mIsPremium ? "PREMIUM" : "NOT PREMIUM"));
 
// (2)
// First find out which subscription is auto renewing
Purchase gasMonthly = inventory.getPurchase(SKU_INFINITE_GAS_MONTHLY);
Purchase gasYearly = inventory.getPurchase(SKU_INFINITE_GAS_YEARLY);
 
// (3)
// Check for gas delivery -- if we own gas, we should fill up the tank immediately
Purchase gasPurchase = inventory.getPurchase(SKU_GAS);
consume(SKU_GAS)
updateUi();
Log.d(TAG, "Initial inventory query finished; enabling main UI.");
}
};

As demonstrated in the above example and in Part 1, the product with the ID SKU_PREMIUM is searched for among the updated purchases, and in case the value of premiumPurchase is not NULL, this non-consumable product is delivered to the user.

In Part 2, the search is performed among subscription products and in case the monthly or yearly subscription is active for the user, the value of gasMonthly or gasYearly assumes a different values than NULL.

In Part 3, the SKU_GAS parameter is searched for among consumable products. In case the gasPurchase parameter of the product is not NULL, the product is consumed and then delivered to the user. The term Consume will further be discussed in the following:

DescriptionsTypeName
Product Type (inapp/subs)StringmItemType
Purchase invoice numberStringmOrderId
Package name of your appStringmPackageName
Product IDStringmSku
Timestamp of the purchase timelongmPurchaseTime
Purchase state (consumed/unconsumed)intmPurchaseState
Developer payloadStringmDeveloperPayload
Product purchase tokenStringmToken
The JSON string returned from the Myket serverStringmOriginalJson
The signature of the mOriginalJson stringStringmSignature
Determines whether Auto Renew is activebooleanmIsAutoRenewing

queryInventoryAsync is the first request called after connecting to the Myket service. Therefore, the general body of the startSetup method is as follows (assuming the lists subSkus and itemSkus hold the product IDs and mGotInventoryListener is implemented):

mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
public void onIabSetupFinished(IabResult result) {
Log.d(TAG, "Setup finished.");
 
if (!result.isSuccess()) {
// Oh noes, there was a problem.
complain("Problem setting up in-app billing: " + result);
return;
}
 
// Have we been disposed of in the meantime? If so, quit.
if (mHelper == null) return;
 
Log.d(TAG, "Setup successful. Querying inventory.");
try {
mHelper.queryInventoryAsync(true, itemSkus, subSkus, mGotInventoryListener);
} catch (IabAsyncInProgressException e) {
complain("Error querying inventory. Another async operation in progress.");
}
}
});
}
try {
mHelper.consumeAsync(purchase, mConsumeFinishedListener);
} catch (IabAsyncInProgressException e) {
complain("Error consuming gas. Another async operation in progress.");
}

Important notice

If the queryInventory is not called at the start of app execution, user payments may be made while no products are delivered. Some developers use a button named “Updating Purchases” to call queryInventory in their app to update the purchases. Note that you still need to update the user purchases at the beginning of app execution even if this button is implemented. This is a common problem faced by developers of in-app purchase services.

Initializing purchase process (launchPurchaseFlow)

Assume your app is launched and successfully connected to the Myket service. The user then opens the shopping section of your app and is provided with a list of your products and their prices (the list that was created using MSkuMap in the class Inventory). A purchase button is implemented for each item in the list, by pressing which the user enters the purchase process. The start of the purchase process for all in-app products using the launchPurchaseFlow in the class IabHelper is as follows:

try {
 
mHelper.launchPurchaseFlow(this, SKU_PREMIUM, RC_REQUEST, mPurchaseFinishedListener, payload);
 
} catch (IabAsyncInProgressException e) {
 
complain("Error launching purchase flow. Another async operation in progress.");
 
}

The method launchPurchaseFlow accepts the following parameters:

Activity act: the activity of your app, within which the purchase process is conducted.

String sku: the product ID of the in-app product you intend to purchase.

int requestCode: an Integer as a requestCode to check in the onActivityResult.

OnIabPurchaseFinishedListener listener: a callback of type OnIabPurchaseFinishedListener reporting the end of a purchase process.

String developerPayload: the Developer payload of this purchase.

Upon starting a purchase process using launchPurchaseFlow, an activity of Myket is opened on your app and displays the product details to the user along with the payment gateways. After a failed or successful purchase, Myket calls the method onActivityResult of your app through the requestCode you already set in the lanchPurchaseFlow. You then must inform the IabHelper of this event through the following procedure:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
Log.d(TAG, "onActivityResult(" + requestCode + "," + resultCode + "," + data);
if (mHelper == null) return;
 
// Pass on the activity result to the helper for handling
if (!mHelper.handleActivityResult(requestCode, resultCode, data)) {
super.onActivityResult(requestCode, resultCode, data);
}
else {
Log.d(TAG, "onActivityResult handled by IABUtil.");
}
}

Then IapHelper calls OnIabPurchaseFinishedListener:

IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener = new IabHelper.OnIabPurchaseFinishedListener() {
public void onIabPurchaseFinished(IabResult result, Purchase purchase) {
Log.d(TAG, "Purchase finished: " + result + ", purchase: " + purchase);
 
// if we were disposed of in the meantime, quit.
if (mHelper == null) return
 
if (result.isFailure()) {
complain("Error purchasing: " + result);
setWaitScreen(false);
return;
}
 
Log.d(TAG, "Purchase successful.");
 
// handling purchase
}
};


After a successful purchase, you should deliver the product to the user according to the product model. You need to deliver non-consumable or subscription products to the user after a successful onIabPurchaseFinished. In case your product is a consumable, you must first consume it and then deliver it to the user. Consuming a product prevents it from being returned in the queryInventory list (updated products). Therefore, do not consume non-consumable and subscription products.

In order to consume a product, use the method consumeAsync along with a Purchase and a Callback of type OnConsumeFinishedListener:

try {
mHelper.consumeAsync(purchase, mConsumeFinishedListener);
} catch (IabAsyncInProgressException e) {
complain("Error consuming gas. Another async operation in progress.");
}

 A sample of the OnConsumeFinishedListener is implemented as follows:

IabHelper.OnConsumeFinishedListener mConsumeFinishedListener = new IabHelper.OnConsumeFinishedListener() {
public void onConsumeFinished(Purchase purchase, IabResult result) {
Log.d(TAG, "Consumption finished. Purchase: " + purchase + ", result: " + result);
 
// if we were disposed of in the meantime, quit.
if (mHelper == null) return;
 
if (result.isSuccess()) {
// successfully consumed, so we apply the effects of the item in our
// game world's logic, which in our case means filling the gas tank a bit
Log.d(TAG, "Consumption successful. Provisioning.");}else {
complain("Error while consumuing" + result);
}
Log.d(TAG, "End consumption flow.");
}
};

In the onConsumeFinished method, similar to onIabPurchaseFinished, a Purchase for the purchase details and an IabResult for the Consume result are returned. In case isSuccess field is TRUE, you may deliver the product to the user (increase their coins for instance).

Sending the purchase information to the server

In case the product delivery in your app is performed on the server side, you need to send the user and purchase information to the server. It is recommended to do so in the OnConsumeFinishedListener after a successful Consume. To this end, it suffices to send the purchase information to the server using the method getOriginalJson in the class Purchase. Note that the field token in this Json is unique for all purchases in your app and hence may be used as a key. Note that it is necessary to send the Signature value along with this Json using the method getSignature in the class Purchase. Then, you need to match these two using the public key provided to you in the Myket Developers Panel.

Preparing the app for exit (onDestroy)

Copy the following code in the onDestory of your app activity, where an Instance of the IabHelper is stored:

@Override
public void onDestroy() {
super.onDestroy();
 
Log.d(TAG, "Destroying helper.");
if (mHelper != null) {
mHelper.disposeWhenFinished();
mHelper = null;
}
}

Trivial Drive

We made some modifications to the Google’s TrivialDrive, which is an educational app, to connect it to the Myket in-app purchase service. You may download its source code from here or view it in Github.

*

Four buttons are available in this app. You may play the game using the green button (Drive) and see that the fuel level decreases. You may no longer play the game if your fuel reaches zero, in which case you need to purchase fuel. Use the yellow button (buy GAS) to by fuel. Note that the fuel is considered a consumable product in this example. The blue button (Update my car) can be used to purchase non-consumable products to never run out of fuel again. The red button (Get Infinite GAS) can be used to buy a one-year or one-month subscription.

Security Concerns

 You need to make sure of the following security issues to prevent unauthorized access to premium features of your app.

Storing the contents

Use a server to store the premium contents of your app and transfer the contents to the client using a webserver after a purchase is made by the user. After receiving the contents from the server, use a key uniquely generated for the respective device to access the contents. Note that your contents are not safe within the APK file and can be accessed.

Confirming the digital signature on the server

When sending information to the server of your app, send the Json string along with the signature received from Myket (using the methods getSignature and getOriginalJson in the class Purchase). Then, confirm the digital signature in the server using the PublicKey. The process of confirming the digital signature is performed in the method verifyPurchase in the class Security. The same procedure should be exactly followed on the server-side:

public static boolean verifyPurchase(String base64PublicKey, String signedData, String signature) {
if (TextUtils.isEmpty(signedData) || TextUtils.isEmpty(base64PublicKey) ||
TextUtils.isEmpty(signature)) {
Log.e(TAG, "Purchase verification failed: missing data.");
return false;
}
 
PublicKey key = Security.generatePublicKey(base64PublicKey);
return Security.verify(key, signedData, signature);
}

Code obfuscation

Protect your app from malware by applying obfuscation to your code. Tools such as ProGuard can be used for this purpose. Moreover, do not hardcode sensitive values such as the public key as a string in your code and try to generate them during runtime (by XORing with several other strings). Note that the support codes provided to you are also available to hackers; therefore, try to alter the project structure and order of the methods to achieve a different and unique pattern to make it more difficult for the hackers finding them.

Using the developer payload

The Myket in-app purchase service allows you to send a string known as Developer Payload along with your purchase request. This string can be considered a unique ID for this purchase. After completing the purchase, this string is returned to your app in the class Purchase. In addition, Myket returns this string along with other purchase details when you update your purchases.

You should use a string token to help your app identify the user who has made the purchase. Therefore, you can later verify whether a purchase is legitimate for a user. This string can be completely random for consumable products. However, in the case of non-consumable or subscription products, you need to use a string which uniquely identifies a user so that you can later confirm the respective user has purchased the product.

Upon receiving the response from Myket, make sure the Developer Payload returned to you with the purchase details is the one you previously sent in the beginning of the purchase process. It is recommended to carry out this validation procedure on your server for increased security.

Always check the permission to access the contents

For increased security, when the app opens, it is recommended to always ask Myket whether access is granted. Never store this on the device.

ProGuard Settings

In case of using ProGuard to obfuscate your codes, remember to add the following code to the ProGuard settings:

keep class com.android.vending.billing


Pre-release testing

In order to test the Myket in-app purchase scenarios, reserve the Packagename of your app in the Developers Panel or upload the APK of your app so that you may have access to the public key assigned to your app. Then, add one or more inexpensive in-app products to test different scenarios. Note that you don’t need to release the app or have the manager’s permission to test in-app purchases.

Was this article helpful?
Dislike 0