Wednesday, June 5, 2013

Android In-App Integration Tutorial

Android In-App Integration Tutorial

Today I am going post about In-App Billing.
In-app products are the digital goods that you offer for sale from inside your application to users. Examples of digital goods includes in-game currency, application feature upgrades that enhance the user experience, and new content for your application.

Below i explained step by step. How to Integrate In-App Billing in Your Application.

*Note.
Don't Test In-App In emulator or unsigned application.

AndroidManifest.xml file you need to give permission 
<uses-permission android:name="com.android.vending.BILLING" />

AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.vj.test"
    android:versionCode="9"
    android:versionName="1.9" >

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

    <application
        android:icon="@drawable/icon"
        android:label="@string/app_name" >
        <activity
            android:name="com.vj.test.Mainactivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service android:name="com.vj.test.BillingService" />

        <receiver android:name="com.vj.test.BillingReceiver" >
            <intent-filter>
                <action android:name="com.android.vending.billing.IN_APP_NOTIFY" />
                <action android:name="com.android.vending.billing.RESPONSE_CODE" />
                <action android:name="com.android.vending.billing.PURCHASE_STATE_CHANGED" />
            </intent-filter>
        </receiver>
    </application>

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="16" />


</manifest>


Then come to Activity class

here you need to add your in-app product ID
private static final CatalogEntry[] CATALOG = new CatalogEntry[] {
                    new CatalogEntry("Your Product ID", R.string.abc123, Managed.MANAGED),
                    new CatalogEntry("Your Product ID ", R.string.abc1234, Managed.MANAGED),
                    new CatalogEntry("Your Product ID ", R.string.ab123200, Managed.MANAGED)

       };


Full Source Code Activity Class 

package com.vj.test;

import java.util.HashSet;
import java.util.Locale;
import java.util.Set;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.text.Html;
import android.text.SpannableStringBuilder;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.SimpleCursorAdapter;
import android.widget.Spinner;
import android.widget.Toast;

import com.vj.test.BillingService.RequestPurchase;
import com.vj.test.BillingService.RestoreTransactions;
import com.vj.test.Consts.PurchaseState;
import com.vj.test.Consts.ResponseCode;

public class Mainactivity extends Activity implements OnClickListener,
             OnItemSelectedListener {
       private static final String TAG = "Dungeons";
       private static final String DB_INITIALIZED = "db_initialized";
       private DungeonsPurchaseObserver mDungeonsPurchaseObserver;
       private Handler mHandler;
       private BillingService mBillingService;
       private Button mBuyButton;
       private Spinner mSelectItemSpinner;
       private PurchaseDatabase mPurchaseDatabase;
       private Cursor mOwnedItemsCursor;
       private Set<String> mOwnedItems = new HashSet<String>();
       private String mPayloadContents = null;
       private static final int DIALOG_CANNOT_CONNECT_ID = 1;
       private static final int DIALOG_BILLING_NOT_SUPPORTED_ID = 2;
       private static final int DIALOG_SUBSCRIPTIONS_NOT_SUPPORTED_ID = 3;

       private enum Managed {
             MANAGED, UNMANAGED, SUBSCRIPTION
       }

       private class DungeonsPurchaseObserver extends PurchaseObserver {
             public DungeonsPurchaseObserver(Handler handler) {
                    super(Mainactivity.this, handler);
             }

             @Override
             public void onBillingSupported(boolean supported, String type) {
                    if (Consts.DEBUG) {
                           Log.i(TAG, "supported: " + supported);
                    }
                    if ((type == null) || type.equals(Consts.ITEM_TYPE_INAPP)) {
                           if (supported) {
                                 restoreDatabase();
                                 mBuyButton.setEnabled(true);
                           } else {
                                 showDialog(DIALOG_BILLING_NOT_SUPPORTED_ID);
                           }
                    } else if (type.equals(Consts.ITEM_TYPE_SUBSCRIPTION)) {
                           mCatalogAdapter.setSubscriptionsSupported(supported);
                    } else {
                           showDialog(DIALOG_SUBSCRIPTIONS_NOT_SUPPORTED_ID);
                    }
             }

             @Override
             public void onPurchaseStateChange(PurchaseState purchaseState,
                           String itemId, int quantity, long purchaseTime,
                           String developerPayload) {
                    if (Consts.DEBUG) {
                           Log.i(TAG, "onPurchaseStateChange() itemId: " + itemId + " "
                                        + purchaseState);
                    }

                    if (developerPayload == null) {
                           logProductActivity(itemId, purchaseState.toString());
                    } else {
                           logProductActivity(itemId, purchaseState + "\n\t"
                                        + developerPayload);
                    }

                    if (purchaseState == PurchaseState.PURCHASED) {
                           mOwnedItems.add(itemId);

                           for (CatalogEntry e : CATALOG) {
                                 if (e.sku.equals(itemId)
                                               && e.managed.equals(Managed.SUBSCRIPTION)) {
                                 }
                           }
                    }
                    mCatalogAdapter.setOwnedItems(mOwnedItems);
                    mOwnedItemsCursor.requery();
             }

             @Override
             public void onRequestPurchaseResponse(RequestPurchase request,
                           ResponseCode responseCode) {
                    if (Consts.DEBUG) {
                           Log.d(TAG, request.mProductId + ": " + responseCode);
                    }
                    if (responseCode == ResponseCode.RESULT_OK) {
                           if (Consts.DEBUG) {
                                 Log.i(TAG, "purchase was successfully sent to server");
                           }
                           logProductActivity(request.mProductId,
                                        "sending purchase request");
                    } else if (responseCode == ResponseCode.RESULT_USER_CANCELED) {
                           if (Consts.DEBUG) {
                                 Log.i(TAG, "user canceled purchase");
                           }
                           logProductActivity(request.mProductId,
                                        "dismissed purchase dialog");
                    } else {
                           if (Consts.DEBUG) {
                                 Log.i(TAG, "purchase failed");
                           }
                           logProductActivity(request.mProductId,
                                        "request purchase returned " + responseCode);
                    }
             }

             @Override
             public void onRestoreTransactionsResponse(RestoreTransactions request,
                           ResponseCode responseCode) {
                    if (responseCode == ResponseCode.RESULT_OK) {
                           if (Consts.DEBUG) {
                                 Log.d(TAG, "completed RestoreTransactions request");
                           }
                           SharedPreferences prefs = getPreferences(Context.MODE_PRIVATE);
                           SharedPreferences.Editor edit = prefs.edit();
                           edit.putBoolean(DB_INITIALIZED, true);
                           edit.commit();
                    } else {
                           if (Consts.DEBUG) {
                                 Log.d(TAG, "RestoreTransactions error: " + responseCode);
                           }
                    }
             }
       }

       private static class CatalogEntry {
             public String sku;
             public int nameId;
             public Managed managed;

             public CatalogEntry(String sku, int nameId, Managed managed) {
                    this.sku = sku;
                    this.nameId = nameId;
                    this.managed = managed;
             }
       }

       private static final CatalogEntry[] CATALOG = new CatalogEntry[] {
                    new CatalogEntry("your product id", R.string.abc123, Managed.MANAGED),
                    new CatalogEntry("your product id", R.string.abc1234, Managed.MANAGED),
                    new CatalogEntry("your product id", R.string.ab123200, Managed.MANAGED)

       };

       private String mItemName;
       private String mSku;
       private Managed mManagedType;
       private CatalogAdapter mCatalogAdapter;

       @SuppressWarnings("deprecation")
       @Override
       public void onCreate(Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
             setContentView(R.layout.main);

             mHandler = new Handler();
             mDungeonsPurchaseObserver = new DungeonsPurchaseObserver(mHandler);
             mBillingService = new BillingService();
             mBillingService.setContext(this);

             mPurchaseDatabase = new PurchaseDatabase(this);
             setupWidgets();
             ResponseHandler.register(mDungeonsPurchaseObserver);
             if (!mBillingService.checkBillingSupported()) {
                    showDialog(DIALOG_CANNOT_CONNECT_ID);
             }

             if (!mBillingService
                           .checkBillingSupported(Consts.ITEM_TYPE_SUBSCRIPTION)) {
                    showDialog(DIALOG_SUBSCRIPTIONS_NOT_SUPPORTED_ID);
             }
       }

       @Override
       protected void onStart() {
             super.onStart();
             ResponseHandler.register(mDungeonsPurchaseObserver);
             initializeOwnedItems();
       }

       @Override
       protected void onStop() {
             super.onStop();
             ResponseHandler.unregister(mDungeonsPurchaseObserver);
       }

       @Override
       protected void onDestroy() {
             super.onDestroy();
             mPurchaseDatabase.close();
             mBillingService.unbind();
       }

       @Override
       protected void onSaveInstanceState(Bundle outState) {
             super.onSaveInstanceState(outState);

       }

       @Override
       protected void onRestoreInstanceState(Bundle savedInstanceState) {
             super.onRestoreInstanceState(savedInstanceState);

       }

       @Override
       protected Dialog onCreateDialog(int id) {
             switch (id) {
             case DIALOG_CANNOT_CONNECT_ID:
                    return createDialog(R.string.cannot_connect_title,
                                 R.string.cannot_connect_message);
             case DIALOG_BILLING_NOT_SUPPORTED_ID:
                    return createDialog(R.string.billing_not_supported_title,
                                 R.string.billing_not_supported_message);
             case DIALOG_SUBSCRIPTIONS_NOT_SUPPORTED_ID:
                    return createDialog(R.string.subscriptions_not_supported_title,
                                 R.string.subscriptions_not_supported_message);
             default:
                    return null;
             }
       }

       private Dialog createDialog(int titleId, int messageId) {
             String helpUrl = replaceLanguageAndRegion(getString(R.string.help_url));
             if (Consts.DEBUG) {
                    Log.i(TAG, helpUrl);
             }
             final Uri helpUri = Uri.parse(helpUrl);

             AlertDialog.Builder builder = new AlertDialog.Builder(this);
             builder.setTitle(titleId)
                           .setIcon(android.R.drawable.stat_sys_warning)
                           .setMessage(messageId)
                           .setCancelable(false)
                           .setPositiveButton(android.R.string.ok, null)
                           .setNegativeButton(R.string.learn_more,
                                        new DialogInterface.OnClickListener() {
                                               @Override
                                               public void onClick(DialogInterface dialog,
                                                            int which) {
                                                     Intent intent = new Intent(Intent.ACTION_VIEW,
                                                                   helpUri);
                                                     startActivity(intent);
                                               }
                                        });
             return builder.create();
       }

       private String replaceLanguageAndRegion(String str) {
             if (str.contains("%lang%") || str.contains("%region%")) {
                    Locale locale = Locale.getDefault();
                    str = str.replace("%lang%", locale.getLanguage().toLowerCase());
                    str = str.replace("%region%", locale.getCountry().toLowerCase());
             }
             return str;
       }

       private void setupWidgets() {

             mBuyButton = (Button) findViewById(R.id.buy_button);
             mBuyButton.setEnabled(false);
             mBuyButton.setOnClickListener(this);
             mSelectItemSpinner = (Spinner) findViewById(R.id.item_choices);
             mCatalogAdapter = new CatalogAdapter(this, CATALOG);
             mSelectItemSpinner.setAdapter(mCatalogAdapter);
             mSelectItemSpinner.setOnItemSelectedListener(this);

             mOwnedItemsCursor = mPurchaseDatabase.queryAllPurchasedItems();
             startManagingCursor(mOwnedItemsCursor);
             String[] from = new String[] {
                           PurchaseDatabase.PURCHASED_PRODUCT_ID_COL,
                           PurchaseDatabase.PURCHASED_QUANTITY_COL };
             int[] to = new int[] { R.id.item_name, R.id.item_quantity };
             new SimpleCursorAdapter(this, R.layout.item_row, mOwnedItemsCursor,
                           from, to);

       }

       private void prependLogEntry(CharSequence cs) {
             SpannableStringBuilder contents = new SpannableStringBuilder(cs);
             contents.append('\n');

       }

       private void logProductActivity(String product, String activity) {
             SpannableStringBuilder contents = new SpannableStringBuilder();
             contents.append(Html.fromHtml("<b>" + product + "</b>: "));
             contents.append(activity);
             prependLogEntry(contents);
       }

       private void restoreDatabase() {
             SharedPreferences prefs = getPreferences(MODE_PRIVATE);
             boolean initialized = prefs.getBoolean(DB_INITIALIZED, false);
             if (!initialized) {
                    mBillingService.restoreTransactions();
                    Toast.makeText(this, R.string.restoring_transactions,
                                 Toast.LENGTH_LONG).show();
             }
       }

       private void initializeOwnedItems() {
             new Thread(new Runnable() {
                    @Override
                    public void run() {
                           doInitializeOwnedItems();
                    }
             }).start();
       }

       private void doInitializeOwnedItems() {
             Cursor cursor = mPurchaseDatabase.queryAllPurchasedItems();
             if (cursor == null) {
                    return;
             }

             final Set<String> ownedItems = new HashSet<String>();
             try {
                    int productIdCol = cursor
                                  .getColumnIndexOrThrow(PurchaseDatabase.PURCHASED_PRODUCT_ID_COL);
                    while (cursor.moveToNext()) {
                           String productId = cursor.getString(productIdCol);
                           ownedItems.add(productId);
                    }
             } finally {
                    cursor.close();
             }

             mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                           mOwnedItems.addAll(ownedItems);
                           mCatalogAdapter.setOwnedItems(mOwnedItems);
                    }
             });
       }

       @Override
       public void onClick(View v) {
             if (v == mBuyButton) {
                    if (Consts.DEBUG) {
                           Log.d(TAG, "buying: " + mItemName + " sku: " + mSku);
                    }

                    if ((mManagedType != Managed.SUBSCRIPTION)
                                 && !mBillingService.requestPurchase(mSku,
                                               Consts.ITEM_TYPE_INAPP, mPayloadContents)) {
                           showDialog(DIALOG_BILLING_NOT_SUPPORTED_ID);
                    } else if (!mBillingService.requestPurchase(mSku,
                                 Consts.ITEM_TYPE_SUBSCRIPTION, mPayloadContents)) {
                           showDialog(DIALOG_SUBSCRIPTIONS_NOT_SUPPORTED_ID);
                    }
             }

       }

       @Override
       public void onItemSelected(AdapterView<?> parent, View view, int position,
                    long id) {
             mItemName = getString(CATALOG[position].nameId);
             mSku = CATALOG[position].sku;
             mManagedType = CATALOG[position].managed;
       }

       @Override
       public void onNothingSelected(AdapterView<?> arg0) {
       }

       private static class CatalogAdapter extends ArrayAdapter<String> {
             private CatalogEntry[] mCatalog;
             private Set<String> mOwnedItems = new HashSet<String>();
             private boolean mIsSubscriptionsSupported = false;

             public CatalogAdapter(Context context, CatalogEntry[] catalog) {
                    super(context, android.R.layout.simple_spinner_item);
                    mCatalog = catalog;
                    for (CatalogEntry element : catalog) {
                           add(context.getString(element.nameId));
                    }
                    setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
             }

             public void setOwnedItems(Set<String> ownedItems) {
                    mOwnedItems = ownedItems;
                    notifyDataSetChanged();
             }

             public void setSubscriptionsSupported(boolean supported) {
                    mIsSubscriptionsSupported = supported;
             }

             @Override
             public boolean areAllItemsEnabled() {
                    return false;
             }

             @Override
             public boolean isEnabled(int position) {

                    CatalogEntry entry = mCatalog[position];
                    if ((entry.managed == Managed.MANAGED)
                                 && mOwnedItems.contains(entry.sku)) {
                           return false;
                    }
                    if ((entry.managed == Managed.SUBSCRIPTION)
                                 && !mIsSubscriptionsSupported) {
                           return false;
                    }
                    return true;
             }

             @Override
             public View getDropDownView(int position, View convertView,
                           ViewGroup parent) {

                    View view = super.getDropDownView(position, convertView, parent);
                    view.setEnabled(isEnabled(position));
                    return view;
             }
       }
}

Then Add In-App product 
Go to Developer account  -> Add Apk Signed Apk -> Then you can able to see In-App product Navigation then click -> Add New Product - >Manage Product -> Enter Product ID and Mandatory field -> Then Make it  Active Product.
Check the below Screen Shot 

*This product  ID only you have to add in above the code for every product.



Then you need base64EncodedPublicKey 

This key you need to add Security.Java class line no 81

Cehck this screen shot 




Copy this key paste it. 

Security.Java class full source code

package com.vj.test;

import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.HashSet;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.text.TextUtils;
import android.util.Log;

import com.example.dungeons.util.Base64;
import com.example.dungeons.util.Base64DecoderException;
import com.vj.test.Consts.PurchaseState;

public class Security {
       private static final String TAG = "Security";

       private static final String KEY_FACTORY_ALGORITHM = "RSA";
       private static final String SIGNATURE_ALGORITHM = "SHA1withRSA";
       private static final SecureRandom RANDOM = new SecureRandom();

       private static HashSet<Long> sKnownNonces = new HashSet<Long>();

       public static class VerifiedPurchase {
             public PurchaseState purchaseState;
             public String notificationId;
             public String productId;
             public String orderId;
             public long purchaseTime;
             public String developerPayload;

             public VerifiedPurchase(PurchaseState purchaseState,
                           String notificationId, String productId, String orderId,
                           long purchaseTime, String developerPayload) {
                    this.purchaseState = purchaseState;
                    this.notificationId = notificationId;
                    this.productId = productId;
                    this.orderId = orderId;
                    this.purchaseTime = purchaseTime;
                    this.developerPayload = developerPayload;
             }
       }

       public static long generateNonce() {
             long nonce = RANDOM.nextLong();
             sKnownNonces.add(nonce);
             return nonce;
       }

       public static void removeNonce(long nonce) {
             sKnownNonces.remove(nonce);
       }

       public static boolean isNonceKnown(long nonce) {
             return sKnownNonces.contains(nonce);
       }

       public static ArrayList<VerifiedPurchase> verifyPurchase(String signedData,
                    String signature) {
             if (signedData == null) {
                    Log.e(TAG, "data is null");
                    return null;
             }
             if (Consts.DEBUG) {
                    Log.i(TAG, "signedData: " + signedData);
             }
             boolean verified = false;
             if (!TextUtils.isEmpty(signature)) {

                    String base64EncodedPublicKey = "Here Your Public KEey";
                    PublicKey key = Security.generatePublicKey(base64EncodedPublicKey);
                    verified = Security.verify(key, signedData, signature);
                    if (!verified) {
                           Log.w(TAG, "signature does not match data.");
                           return null;
                    }
             }

             JSONObject jObject;
             JSONArray jTransactionsArray = null;
             int numTransactions = 0;
             long nonce = 0L;
             try {
                    jObject = new JSONObject(signedData);
                    nonce = jObject.optLong("nonce");
                    jTransactionsArray = jObject.optJSONArray("orders");
                    if (jTransactionsArray != null) {
                           numTransactions = jTransactionsArray.length();
                    }
             } catch (JSONException e) {
                    return null;
             }

             if (!Security.isNonceKnown(nonce)) {
                    Log.w(TAG, "Nonce not found: " + nonce);
                    return null;
             }

             ArrayList<VerifiedPurchase> purchases = new ArrayList<VerifiedPurchase>();
             try {
                    for (int i = 0; i < numTransactions; i++) {
                           JSONObject jElement = jTransactionsArray.getJSONObject(i);
                           int response = jElement.getInt("purchaseState");
                           PurchaseState purchaseState = PurchaseState.valueOf(response);
                           String productId = jElement.getString("productId");
                           @SuppressWarnings("unused")
                           String packageName = jElement.getString("packageName");
                           long purchaseTime = jElement.getLong("purchaseTime");
                           String orderId = jElement.optString("orderId", "");
                           String notifyId = null;
                           if (jElement.has("notificationId")) {
                                 notifyId = jElement.getString("notificationId");
                           }
                           String developerPayload = jElement.optString(
                                        "developerPayload", null);

                           if ((purchaseState == PurchaseState.PURCHASED) && !verified) {
                                 continue;
                           }
                           purchases.add(new VerifiedPurchase(purchaseState, notifyId,
                                        productId, orderId, purchaseTime, developerPayload));
                    }
             } catch (JSONException e) {
                    Log.e(TAG, "JSON exception: ", e);
                    return null;
             }
             removeNonce(nonce);
             return purchases;
       }

       public static PublicKey generatePublicKey(String encodedPublicKey) {
             try {
                    byte[] decodedKey = Base64.decode(encodedPublicKey);
                    KeyFactory keyFactory = KeyFactory
                                 .getInstance(KEY_FACTORY_ALGORITHM);
                    return keyFactory
                                 .generatePublic(new X509EncodedKeySpec(decodedKey));
             } catch (NoSuchAlgorithmException e) {
                    throw new RuntimeException(e);
             } catch (InvalidKeySpecException e) {
                    Log.e(TAG, "Invalid key specification.");
                    throw new IllegalArgumentException(e);
             } catch (Base64DecoderException e) {
                    Log.e(TAG, "Base64 decoding failed.");
                    throw new IllegalArgumentException(e);
             }
       }

       public static boolean verify(PublicKey publicKey, String signedData,
                    String signature) {
             if (Consts.DEBUG) {
                    Log.i(TAG, "signature: " + signature);
             }
             Signature sig;
             try {
                    sig = Signature.getInstance(SIGNATURE_ALGORITHM);
                    sig.initVerify(publicKey);
                    sig.update(signedData.getBytes());
                    if (!sig.verify(Base64.decode(signature))) {
                           Log.e(TAG, "Signature verification failed.");
                           return false;
                    }
                    return true;
             } catch (NoSuchAlgorithmException e) {
                    Log.e(TAG, "NoSuchAlgorithmException.");
             } catch (InvalidKeyException e) {
                    Log.e(TAG, "Invalid key specification.");
             } catch (SignatureException e) {
                    Log.e(TAG, "Signature exception.");
             } catch (Base64DecoderException e) {
                    Log.e(TAG, "Base64 decoding failed.");
             }
             return false;
       }
}


Then publish the application 
Wait for several hours.

Then download application enjoy with In-App Billing













Check out this may be help you

Related Posts Plugin for WordPress, Blogger...