// ref: http://developer.android.com/google/play/billing/billing_integrate.html
package com.baroq.pico.google;
import org.apache.cordova.api.CordovaPlugin;
import org.apache.cordova.api.PluginResult;
import org.apache.cordova.api.CallbackContext;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;
import com.baroq.pico.google.iab.IabHelper;
import com.baroq.pico.google.iab.IabResult;
import com.baroq.pico.google.iab.Inventory;
import com.baroq.pico.google.iab.Purchase;
import java.util.ArrayList;
import java.util.List;
public class InAppBilling extends CordovaPlugin{
private static final String TAG = "PICO-GOOG-IAP";
private static final String PUBLIC_KEY = "GET_THIS_FROM_GOOGLE_PLAY_DEVELOPER_CONSOLE_SERVICES_AND_API";
private static final int ACT_CB_IAP = 10001;
private static final int ACT_CB_SUB = 10002;
private static final String ACTION_OPEN = "open";
private static final String ACTION_CLOSE = "close";
private static final String ACTION_INV = "inventory";
private static final String ACTION_BUY = "buy";
private static final String ACTION_SUB = "subscribe";
private static final String ACTION_CONSUME = "consume";
// The helper object
IabHelper mHelper;
// Plugin action handler
@Override
public boolean execute(String action, JSONArray data, CallbackContext callbackContext) {
PluginResult pluginResult = new PluginResult(PluginResult.Status.NO_RESULT);
pluginResult.setKeepCallback(true);
try{
if (ACTION_OPEN.equals(action)) {
open(cordova.getActivity(), PUBLIC_KEY, callbackContext);
callbackContext.sendPluginResult(pluginResult);
} else if (ACTION_CLOSE.equals(action)){
close();
callbackContext.success();
} else if (ACTION_INV.equals(action)){
List<String> moreSkus = new ArrayList<String>();
for (int i=0, l=data.length(); i<l; i++) {
moreSkus.add( data.getString(i) );
}
inventory(moreSkus, callbackContext);
callbackContext.sendPluginResult(pluginResult);
} else if (ACTION_BUY.equals(action)){
// buy in app item, data[0]: sku, data[1]: payload
buy(cordova.getActivity(), data.getString(0), data.getString(1), callbackContext);
callbackContext.sendPluginResult(pluginResult);
} else if (ACTION_SUB.equals(action)){
// subscribe in app service, data[0]: sku, data[1]: payload
subscribe(cordova.getActivity(), data.getString(0), data.getString(1), callbackContext);
callbackContext.sendPluginResult(pluginResult);
} else if (ACTION_CONSUME.equals(action)){
consume(new Purchase(IabHelper.ITEM_TYPE_INAPP, data.getString(0), ""), callbackContext);
callbackContext.sendPluginResult(pluginResult);
} else{
callbackContext.error("Unknown action: " + action);
return false;
}
}catch(JSONException ex){
callbackContext.error(ex.getMessage());
return false;
}
return true;
}
@Override
public void onDestroy() {
super.onDestroy();
close();
}
private void open(Activity activity, String key, final CallbackContext callbackContext){
close();
// Create the helper, passing it our context and the public key to verify signatures with
Log.d(TAG, "Creating IAB helper.");
mHelper = new IabHelper(activity, key);
// enable debug logging (for a production application, you should set this to false).
mHelper.enableDebugLogging(true);
Log.d(TAG, "Setup onActivityCallback to this");
cordova.setActivityResultCallback(this);
// Start setup. This is asynchronous and the specified listener
// will be called once setup completes.
Log.d(TAG, "Starting setup.");
mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
public void onIabSetupFinished(IabResult result) {
Log.d(TAG, "Setup finished.");
if (!result.isSuccess()) {
// Oh noes, there was a problem.
callbackContext.error("Problem setting up in-app billing: " + result);
return;
}
// Have we been disposed of in the meantime? If so, quit.
if (mHelper == null) {
callbackContext.error("The billing helper has been disposed");
return;
}
// IAB is fully set up. Now, let's get an inventory of stuff we own.
Log.d(TAG, "Setup successful.");
callbackContext.success("Init successful");
}
});
}
private void close(){
if (null != mHelper){
mHelper.dispose();
mHelper = null;
}
}
private void inventory(List<String> moreSkus, final CallbackContext callbackContext){
Log.d(TAG, "Querying inventory.");
mHelper.queryInventoryAsync(true, moreSkus, new IabHelper.QueryInventoryFinishedListener() {
public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
Log.d(TAG, "Query inventory finished.");
// Have we been disposed of in the meantime? If so, quit.
if (mHelper == null) {
callbackContext.error("The billing helper has been disposed");
return;
}
// Is it a failure?
if (result.isFailure()) {
callbackContext.error("Failed to query inventory: " + result);
return;
}
Log.d(TAG, "detail list size: "+inventory.jsonSkuDetailsList.size());
JSONObject json = new JSONObject();
JSONArray ownedSkus = new JSONArray();
JSONArray purchaseDataList = new JSONArray();
JSONArray signatureList = new JSONArray();
JSONArray skuDetailsList = new JSONArray();
int i, l;
ArrayList<String> list1, list2, list3;
try{
list1 = inventory.jsonOwnedSkus;
list2 = inventory.jsonPurchaseDataList;
list3 = inventory.jsonSignatureList;
for(i=0, l=list1.size(); i<l; i++){
ownedSkus.put(list1.get(i));
purchaseDataList.put(new JSONObject(list2.get(i)));
signatureList.put(list3.get(i));
}
list1 = inventory.jsonSkuDetailsList;
for(i=0, l=list1.size(); i<l; i++){
skuDetailsList.put(new JSONObject(list1.get(i)));
}
json.put("ownedSkus", ownedSkus);
json.put("purchaseDataList", purchaseDataList);
json.put("signatureList", signatureList);
json.put("skuDetailsList", skuDetailsList);
}catch(JSONException ex){
callbackContext.error("Failed to contruct inventory json: " + ex);
return;
}
Log.d(TAG, "Query inventory was successful.");
PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, json);
callbackContext.sendPluginResult(pluginResult);
}
});
}
private void buy(Activity act, String sku, String payload, CallbackContext callbackContext){
if (mHelper == null){
callbackContext.error("Did you forget to initialize the plugin?");
return;
}
Log.d(TAG, "launching purchase flow for item purchase.");
purchase(act, sku, payload, IabHelper.ITEM_TYPE_INAPP, ACT_CB_IAP, callbackContext);
}
private void subscribe(Activity act, String sku, String payload, CallbackContext callbackContext){
if (mHelper == null){
callbackContext.error("Did you forget to initialize the plugin?");
return;
}
if (!mHelper.subscriptionsSupported()) {
callbackContext.error("Subscriptions not supported on your device yet. Sorry!");
return;
}
Log.d(TAG, "Launching purchase flow for subscription.");
purchase(act, sku, payload, IabHelper.ITEM_TYPE_SUBS, ACT_CB_SUB, callbackContext);
}
private void purchase(Activity act, String sku, String payload, String itemType, int cbId, final CallbackContext callbackContext){
mHelper.launchPurchaseFlow(act, sku, itemType, cbId,
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) {
callbackContext.error("The billing helper has been disposed");
return;
}
if (result.isFailure()) {
callbackContext.error("Error purchasing: " + result);
return;
}
JSONObject json = new JSONObject();
try{
json.put("purchaseData", new JSONObject(purchase.getOriginalJson()));
json.put("itemType", purchase.getItemType());
json.put("signature", purchase.getSignature());
}catch(JSONException ex){
callbackContext.error("Failed to contruct purchase json: " + ex);
return;
}
Log.d(TAG, "Query purchase was successful.");
PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, json);
callbackContext.sendPluginResult(pluginResult);
}
}, payload);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
Log.d(TAG, "onActivityResult(" + requestCode + "," + resultCode + "," + data);
// Pass on the activity result to the helper for handling
if (!mHelper.handleActivityResult(requestCode, resultCode, data)) {
// not handled, so handle it ourselves (here's where you'd
// perform any handling of activity results not related to in-app
// billing...
super.onActivityResult(requestCode, resultCode, data);
} else {
Log.d(TAG, "onActivityResult handled by "+TAG);
}
}
private void consume(Purchase purchase, final CallbackContext callbackContext){
if (mHelper == null){
callbackContext.error("Did you forget to initialize the plugin?");
return;
}
mHelper.consumeAsync(purchase, new IabHelper.OnConsumeFinishedListener() {
public void onConsumeFinished(Purchase purchase, IabResult result) {
Log.d(TAG, "Consumption finished. Purchase: " + purchase + ", result: " + result);
// We know this is the "gas" sku because it's the only one we consume,
// so we don't check which sku was consumed. If you have more than one
// sku, you probably should check...
if (result.isSuccess()) {
// successfully consumed, so we apply the effects of the item in our
// game world's logic
// remove the item from the inventory
Log.d(TAG, "Consumption successful. .");
callbackContext.success(purchase.getOriginalJson());
} else {
callbackContext.error("Error while consuming: " + result);
}
}
});
}
};