Android : Issue with Google In App Purchase implementation in application

on Friday, August 1, 2014


I have developed an application that have payment functionality so i choose Google In App purchase for that so i did research so find one tutorial but i got some error


my code is


Helper class



package rk.contact.inapppurchase;

import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
import android.util.Log;

import java.util.ArrayList;

import rk.android.vending.billing.IMarketBillingService;

public class BillingHelper {

private static final String TAG = "BillingService";

private static IMarketBillingService mService;
private static Context mContext;
private static Handler mCompletedHandler;

public static BillingSecurity.VerifiedPurchase latestPurchase;

protected static void instantiateHelper(Context context,
IMarketBillingService service) {
mService = service;
mContext = context;
}

public static void setCompletedHandler(Handler handler) {
mCompletedHandler = handler;
}

public static boolean isBillingSupported() {
if (amIDead()) {
return false;
}
Bundle request = makeRequestBundle("CHECK_BILLING_SUPPORTED");
if (mService != null) {
try {
Bundle response = mService.sendBillingRequest(request);
InAppPurchaseConstant.ResponseCode code = InAppPurchaseConstant.ResponseCode.valueOf((Integer) response
.get("RESPONSE_CODE"));
Log.i(TAG,
"isBillingSupported response was: " + code.toString());
if (InAppPurchaseConstant.ResponseCode.RESULT_OK.equals(code)) {
return true;
} else {
return false;
}
} catch (RemoteException e) {
Log.e(TAG, "isBillingSupported response was: RemoteException",
e);
return false;
}
} else {
Log.i(TAG,
"isBillingSupported response was: BillingService.mService = null");
return false;
}
}

public static void requestPurchase(Context activityContext, String itemId) {
if (amIDead()) {
return;
}
Log.i(TAG, "requestPurchase()");
Bundle request = makeRequestBundle("REQUEST_PURCHASE");
request.putString("ITEM_ID", itemId);
try {
Bundle response = mService.sendBillingRequest(request);

Long requestIndentifier = (Long) response.get("REQUEST_ID");
Log.i(TAG, "current request is:" + requestIndentifier);
InAppPurchaseConstant.ResponseCode responseCode = InAppPurchaseConstant.ResponseCode
.valueOf(responseCodeIndex);
Log.i(TAG,
"REQUEST_PURCHASE Sync Response code: "
+ responseCode.toString()
);

startBuyPageActivity(pendingIntent, new Intent(), activityContext);
} catch (RemoteException e) {
Log.e(TAG, "Failed, internet error maybe", e);
Log.e(TAG, "Billing supported: " + isBillingSupported());
}
}

protected static void getPurchaseInformation(String[] notifyIds) {
if (amIDead()) {
return;
}
Log.i(TAG, "getPurchaseInformation()");
Bundle request = makeRequestBundle("GET_PURCHASE_INFORMATION");

request.putStringArray("NOTIFY_IDS", notifyIds);
try {
Bundle response = mService.sendBillingRequest(request);

// The REQUEST_ID key provides you with a unique request identifier
// for the request
Long requestIndentifier = (Long) response.get("REQUEST_ID");
Log.i(TAG, "current request is:" + requestIndentifier);
// The RESPONSE_CODE key provides you with the status of the request
Integer responseCodeIndex = (Integer) response.get("RESPONSE_CODE");
InAppPurchaseConstant.ResponseCode responseCode = InAppPurchaseConstant.ResponseCode
.valueOf(responseCodeIndex);
Log.i(TAG, "GET_PURCHASE_INFORMATION Sync Response code: "
+ responseCode.toString());

} catch (RemoteException e) {
Log.e(TAG, "Failed, internet error maybe", e);
Log.e(TAG, "Billing supported: " + isBillingSupported());
}
}

protected static void confirmTransaction(String[] notifyIds) {
if (amIDead()) {
return;
}
Log.i(TAG, "confirmTransaction()");
Bundle request = makeRequestBundle("CONFIRM_NOTIFICATIONS");
request.putStringArray("NOTIFY_IDS", notifyIds);
try {
Bundle response = mService.sendBillingRequest(request);

// The REQUEST_ID key provides you with a unique request identifier
// for the request
Long requestIndentifier = (Long) response.get("REQUEST_ID");
Log.i(TAG, "current request is:" + requestIndentifier);

// The RESPONSE_CODE key provides you with the status of the request
Integer responseCodeIndex = (Integer) response.get("RESPONSE_CODE");
InAppPurchaseConstant.ResponseCode responseCode = InAppPurchaseConstant.ResponseCode
.valueOf(responseCodeIndex);

Log.i(TAG, "CONFIRM_NOTIFICATIONS Sync Response code: "
+ responseCode.toString());
} catch (RemoteException e) {
Log.e(TAG, "Failed, internet error maybe", e);
Log.e(TAG, "Billing supported: " + isBillingSupported());
}
}

protected static void restoreTransactionInformation(Long nonce) {
if (amIDead()) {
return;
}
Log.i(TAG, "confirmTransaction()");
Bundle request = makeRequestBundle("RESTORE_TRANSACTIONS");
// The REQUEST_NONCE key contains a cryptographically secure nonce
// (number used once) that you must generate
request.putLong("NONCE", nonce);
try {
Bundle response = mService.sendBillingRequest(request);

// The REQUEST_ID key provides you with a unique request identifier
// for the request
Long requestIndentifier = (Long) response.get("REQUEST_ID");
Log.i(TAG, "current request is:" + requestIndentifier);

// The RESPONSE_CODE key provides you with the status of the request
Integer responseCodeIndex = (Integer) response.get("RESPONSE_CODE");
InAppPurchaseConstant.ResponseCode responseCode = InAppPurchaseConstant.ResponseCode
.valueOf(responseCodeIndex);
Log.i(TAG, "RESTORE_TRANSACTIONS Sync Response code: "
+ responseCode.toString());
} catch (RemoteException e) {
Log.e(TAG, "Failed, internet error maybe", e);
Log.e(TAG, "Billing supported: " + isBillingSupported());
}
}
private static boolean amIDead() {
if (mService == null || mContext == null) {
Log.e(TAG, "BillingHelper not fully instantiated");
return true;
} else {
return false;
}
}

private static Bundle makeRequestBundle(String method) {
Bundle request = new Bundle();
request.putString("BILLING_REQUEST", method);
request.putInt("API_VERSION", 1);
request.putString("PACKAGE_NAME", mContext.getPackageName());
return request;
}

private static void startBuyPageActivity(PendingIntent pendingIntent,

try {
pendingIntent.send(context, 0, intent);
} catch (CanceledException e) {
Log.e(TAG, "startBuyPageActivity CanceledException");
}
}

protected static void verifyPurchase(String signedData, String signature) {
ArrayList<BillingSecurity.VerifiedPurchase> purchases = BillingSecurity.verifyPurchase(
signedData, signature);
latestPurchase = purchases.get(0);

confirmTransaction(new String[]{latestPurchase.notificationId});

if (mCompletedHandler != null) {
mCompletedHandler.sendEmptyMessage(0);
} else {
Log.e(TAG,
"verifyPurchase error. Handler not instantiated. Have you called setCompletedHandler()?");
}
}

public static void stopService() {
mContext.stopService(new Intent(mContext, BillingService.class));
mService = null;
mContext = null;
mCompletedHandler = null;
Log.i(TAG, "Stopping Service");
}
}


Security class



package rk.contact.inapppurchase;

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



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

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;

public class BillingSecurity {
private static final String TAG = "BillingService";

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>();

/**
* A class to hold the verified purchase information.
*/
public static class VerifiedPurchase {
public InAppPurchaseConstant.PurchaseState purchaseState;
public String notificationId;
public String productId;
public String orderId;
public long purchaseTime;
public String developerPayload;

public VerifiedPurchase(InAppPurchaseConstant.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 boolean isPurchased() {
return purchaseState.equals(InAppPurchaseConstant.PurchaseState.PURCHASED);
}

}

/** Generates a nonce (a random number used once). */
public static long generateNonce() {
long nonce = RANDOM.nextLong();
Log.i(TAG, "Nonce generateD: " + nonce);
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;
}
Log.i(TAG, "signedData: " + signedData);
boolean verified = false;
if (!TextUtils.isEmpty(signature)) {

String base64EncodedPublicKey = "*******************";
PublicKey key = BillingSecurity
.generatePublicKey(base64EncodedPublicKey);
verified = BillingSecurity.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);

// The nonce might be null if the user backed out of the buy page.
nonce = jObject.optLong("nonce");
jTransactionsArray = jObject.optJSONArray("orders");
if (jTransactionsArray != null) {
numTransactions = jTransactionsArray.length();
}
} catch (JSONException e) {
return null;
}

if (!BillingSecurity.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");
InAppPurchaseConstant.PurchaseState purchaseState = InAppPurchaseConstant.PurchaseState.valueOf(response);
String productId = jElement.getString("productId");
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 the purchase state is PURCHASED, then we require a
// verified nonce.
if (purchaseState == InAppPurchaseConstant.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, "Base64DecoderException.", e);
return null;
}
}
public static boolean verify(PublicKey publicKey, String signedData,
String signature) {
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, "Base64DecoderException.", e);
}
return false;
}
}


Main Activity



package rk.contact;

import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;

import rk.contact.inapppurchase.BillingHelper;
import rk.contact.inapppurchase.BillingService;
import rk.contact.utils.Constant;
import rk.contact.utils.Global;


public class SubscribeActivity extends ActionBarActivity implements View.OnClickListener {

private TextView title, info, date;
private Button oneMonth, threeMonth, sixMonth, yearly;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_subscribe);

getSupportActionBar().setDisplayHomeAsUpEnabled(true);
this.getSupportActionBar().setDisplayShowCustomEnabled(true);
this.getSupportActionBar().setDisplayShowTitleEnabled(false);
LayoutInflater inflator = LayoutInflater.from(this);
View v = inflator.inflate(R.layout.titleview, null);
TextView tv = (TextView) v.findViewById(R.id.title);

getSupportActionBar().setCustomView(v);

// ((RelativeLayout) findViewById(R.id.expired)).setVisibility(View.GONE);
if (Global.ExpiryDate.contains("none")) {
((RelativeLayout) findViewById(R.id.expired)).setVisibility(View.GONE);
tv.setText("Subscribe");
} else {
((RelativeLayout) findViewById(R.id.subscribe)).setVisibility(View.GONE);
tv.setText("Subscription");
}

title = (TextView) findViewById(R.id.title);
info = (TextView) findViewById(R.id.info);
date = (TextView) findViewById(R.id.date);

date.setText(Global.ExpiryDate);

oneMonth = (Button) findViewById(R.id.oneMonth);
threeMonth = (Button) findViewById(R.id.threeMonth);
sixMonth = (Button) findViewById(R.id.sixMonth);
yearly = (Button) findViewById(R.id.yearly);

oneMonth.setText("One Month ($" + Constant.OneMonthFee + ")");
threeMonth.setText("3 months ($" + Constant.ThreeMonthFee + ")");
sixMonth.setText("6 months ($" + Constant.SixMonthFee + ")");
yearly.setText("One Year ($" + Constant.YearlyFee + ")");

oneMonth.setOnClickListener(this);
threeMonth.setOnClickListener(this);
sixMonth.setOnClickListener(this);
yearly.setOnClickListener(this);


startService(new Intent(this, BillingService.class));
BillingHelper.setCompletedHandler(mTransactionHandler);

}

private String TAG="--------------------------------------------------------------";
public Handler mTransactionHandler = new Handler(){
public void handleMessage(android.os.Message msg) {
Log.i(TAG, "Transaction complete");
Log.i(TAG, "Transaction status: "+BillingHelper.latestPurchase.purchaseState);
Log.i(TAG, "Item purchased is: "+BillingHelper.latestPurchase.productId);

if(BillingHelper.latestPurchase.isPurchased()){
showItem();
}
};

};

private void showItem() {
//purchaseableItem.setVisibility(View.VISIBLE);
}

@Override
public void onClick(View v) {

if (v.getId() == oneMonth.getId()) {

if (BillingHelper.isBillingSupported()) {
BillingHelper.requestPurchase(this, "android.test.purchased");
} else {
Toast.makeText(this, "Can't purchase on this device", Toast.LENGTH_LONG);
}
// OpenGoogleWalletActivity("One Month", Constant.NoOfDay, Constant.OneMonthFee);
} else if (v.getId() == threeMonth.getId()) {
if (BillingHelper.isBillingSupported()) {
BillingHelper.requestPurchase(this, "android.test.purchased");
} else {
Toast.makeText(this, "Can't purchase on this device", Toast.LENGTH_LONG);
}
// OpenGoogleWalletActivity("Three Month", 3 * Constant.NoOfDay, Constant.ThreeMonthFee);
} else if (v.getId() == sixMonth.getId()) {
if (BillingHelper.isBillingSupported()) {
BillingHelper.requestPurchase(this, "android.test.purchased");
} else {
Toast.makeText(this, "Can't purchase on this device", Toast.LENGTH_LONG);
}
// OpenGoogleWalletActivity("Six Month", 6 * Constant.NoOfDay, Constant.SixMonthFee);
} else if (v.getId() == yearly.getId()) {
if (BillingHelper.isBillingSupported()) {
BillingHelper.requestPurchase(this, "android.test.purchased");
} else {
Toast.makeText(this, "Can't purchase on this device", Toast.LENGTH_LONG);
}
// OpenGoogleWalletActivity("One Year", 12 * Constant.NoOfDay, Constant.YearlyFee);
}
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {

switch (item.getItemId()) {
case android.R.id.home:
finish();
return true;

default:
return super.onOptionsItemSelected(item);
}
}

public void OpenGoogleWalletActivity(String packageName, int noOfDay, String amount) {
Intent intent = new Intent(this, CheckOutWithGoogle.class);
intent.putExtra(Constant.SubscribePackageName, packageName);
intent.putExtra(Constant.NoOfDays, noOfDay);
intent.putExtra(Constant.Amount, amount);
startActivity(intent);
}

@Override
protected void onDestroy() {
BillingHelper.stopService();
super.onDestroy();
}

}

0 comments:

Post a Comment