package io.vigour.store;
import android.app.Activity;
import android.content.Intent;
import android.util.Log;
import org.apache.cordova.CallbackContext;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
import io.vigour.store.util.IabHelper;
import io.vigour.store.util.IabResult;
import io.vigour.store.util.Inventory;
import io.vigour.store.util.Purchase;
import io.vigour.store.util.SkuDetails;
/**
* Created by andrej on 03/09/14.
*/
public class PlayStoreHandler extends StoreHandler
{
private static String TAG = PlayStoreHandler.class.getSimpleName();
private final Boolean ENABLE_DEBUG_LOGGING = true;
// (arbitrary) request code for the purchase flow
static final int RC_REQUEST = 10001;
private List<String> skus = new ArrayList<String>();
// The helper object
IabHelper mHelper;
// A quite up to date inventory of available items and purchase items
Inventory myInventory;
public PlayStoreHandler(VigourIoStore ioStore)
{
super(ioStore);
}
@Override
void getType(final CallbackContext callbackContext) throws Exception
{
callbackContext.success("{\"type\":1}");
}
@Override
void init(JSONArray data, final CallbackContext callbackContext) throws Exception
{
this.callbackContext = callbackContext;
final List<String> skus = new ArrayList<String>();
for (int i=0;i<data.length();i++){
skus.add(data.get(i).toString());
Log.d(TAG, "Product SKU Added: "+data.get(i).toString());
}
Log.d(TAG, "init start");
// Some sanity checks to see if the developer (that's you!) really followed the
// instructions to run this plugin
Activity activity = ioStore.cordova.getActivity();
int resId = activity.getResources().getIdentifier("billing_key", "string", activity.getPackageName());
String base64EncodedPublicKey = activity.getString(resId);
if (base64EncodedPublicKey == null || base64EncodedPublicKey.isEmpty())
{
callbackContext.error("Failed to read billing_key from strings.xml");
return;
}
// Create the helper, passing it our context and the public key to verify signatures with
Log.d(TAG, "Creating IAB helper.");
mHelper = new IabHelper(ioStore.cordova.getActivity().getApplicationContext(), base64EncodedPublicKey);
// enable debug logging (for a production application, you should set this to false).
mHelper.enableDebugLogging(ENABLE_DEBUG_LOGGING);
// 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. " + result);
if (!result.isSuccess()) {
// Oh no, 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");
}
// Hooray, IAB is fully set up. Now, let's get an inventory of stuff we own.
if(skus.size() <= 0){
Log.d(TAG, "Setup successful. Querying inventory.");
mHelper.queryInventoryAsync(mGotInventoryListener);
}else{
Log.d(TAG, "Setup successful. Querying inventory w/ SKUs.");
mHelper.queryInventoryAsync(true, skus, mGotInventoryListener);
}
}
});
}
@Override
void getProductDetails(JSONArray data, final CallbackContext callbackContext) throws Exception
{
this.callbackContext = callbackContext;
for (int i=0;i<data.length();i++){
skus.add(data.get(i).toString());
Log.d(TAG, "Product SKU Added: "+data.get(i).toString());
}
if (mHelper == null) {
callbackContext.error("Billing plugin was not initialized");
return;
}
Log.d(TAG, "Beginning Sku(s) Query!");
mHelper.queryInventoryAsync(true, skus, mGotDetailsListener);
}
@Override
void buy(JSONArray data, final CallbackContext callbackContext) throws Exception
{
this.callbackContext = callbackContext;
final String sku = data.getString(0);
/* TODO: for security, generate your payload here for verification. See the comments on
* verifyDeveloperPayload() for more info. Since this is a sample, we just use
* an empty string, but on a production app you should generate this. */
final String payload = generatePayload(sku);
if (mHelper == null){
callbackContext.error("Billing plugin was not initialized");
return;
}
ioStore.cordova.setActivityResultCallback(ioStore);
mHelper.launchPurchaseFlow(ioStore.cordova.getActivity(), sku, RC_REQUEST,
mPurchaseFinishedListener, payload);
}
@Override
void subscribe(JSONArray data, final CallbackContext callbackContext) throws Exception
{
this.callbackContext = callbackContext;
final String sku = data.getString(0);
if (mHelper == null){
callbackContext.error("Billing plugin was not initialized");
return;
}
if (!mHelper.subscriptionsSupported()) {
callbackContext.error("Subscriptions not supported on your device yet. Sorry!");
return;
}
/* TODO: for security, generate your payload here for verification. See the comments on
* verifyDeveloperPayload() for more info. Since this is a sample, we just use
* an empty string, but on a production app you should generate this. */
final String payload = generatePayload(sku);
ioStore.cordova.setActivityResultCallback(ioStore);
Log.d(TAG, "Launching purchase flow for subscription.");
mHelper.launchPurchaseFlow(ioStore.cordova.getActivity(), sku, IabHelper.ITEM_TYPE_SUBS, RC_REQUEST, mPurchaseFinishedListener, payload);
}
@Override
void getPurchases(JSONArray data, final CallbackContext callbackContext) throws Exception
{
this.callbackContext = callbackContext;
if (myInventory == null) {
callbackContext.error("Billing plugin was not initialized");
return;
}
// Convert the java list to json
JSONArray skus = new JSONArray();
for (Purchase p : myInventory.getAllPurchases()) {
JSONObject jsonSku = new JSONObject();
jsonSku.put(p.getSku(), new JSONObject(p.getOriginalJson()));
skus.put(jsonSku);
}
// Call the javascript back
callbackContext.success(skus);
}
String generatePayload(String sku)
{
return "";
}
/** Verifies the developer payload of a purchase. */
boolean verifyDeveloperPayload(Purchase p) {
return true;
/*
String payload = p.getDeveloperPayload();
if (payload == null) {
return false;
}
return payload.equalsIgnoreCase(generatePayload(p.getSku()));
*/
/*
* TODO: verify that the developer payload of the purchase is correct. It will be
* the same one that you sent when initiating the purchase.
*
* WARNING: Locally generating a random string when starting a purchase and
* verifying it here might seem like a good approach, but this will fail in the
* case where the user purchases an item on one device and then uses your app on
* a different device, because on the other device you will not have access to the
* random string you originally generated.
*
* So a good developer payload has these characteristics:
*
* 1. If two different users purchase an item, the payload is different between them,
* so that one user's purchase can't be replayed to another user.
*
* 2. The payload must be such that you can verify it even when the app wasn't the
* one who initiated the purchase flow (so that items purchased by the user on
* one device work on other devices owned by the user).
*
* Using your own server to store and verify developer payloads across app
* installations is recommended.
*/
}
IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener = new IabHelper.OnIabPurchaseFinishedListener() {
public void onIabPurchaseFinished(IabResult result, Purchase purchase) {
Log.d(TAG, "Purchase finished: " + result + ", purchase: " + purchase);
// Have we been disposed of in the meantime? If so, quit.
if (mHelper == null) {
callbackContext.error("The billing helper has been disposed");
}
if (result.isFailure() ) {
callbackContext.error("Error purchasing: " + result);
return;
}
if (!verifyDeveloperPayload(purchase)) {
callbackContext.error("Error purchasing. Authenticity verification failed.");
return;
}
Log.d(TAG, "Purchase successful.");
// add the purchase to the inventory
myInventory.addPurchase(purchase);
try {
callbackContext.success(new JSONObject(purchase.getOriginalJson()));
} catch (JSONException e) {
callbackContext.error("Could not create JSON object from purchase object");
}
}
};
// Check if there is any errors in the iabResult and update the inventory
private Boolean hasErrorsAndUpdateInventory(IabResult result, Inventory inventory){
if (result.isFailure()) {
callbackContext.error("Failed to query inventory: " + result);
return true;
}
// Have we been disposed of in the meantime? If so, quit.
if (mHelper == null) {
callbackContext.error("The billing helper has been disposed");
return true;
}
// Update the inventory
myInventory = inventory;
return false;
}
// Listener that's called when we finish querying the items and subscriptions we own
IabHelper.QueryInventoryFinishedListener mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener() {
public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
Log.d(TAG, "Inside mGotInventoryListener");
if (hasErrorsAndUpdateInventory(result, inventory)) return;
Log.d(TAG, "Query inventory was successful.");
callbackContext.success();
}
};
// Listener that's called when we finish querying the details
IabHelper.QueryInventoryFinishedListener mGotDetailsListener = new IabHelper.QueryInventoryFinishedListener() {
public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
Log.d(TAG, "Inside mGotDetailsListener");
if (hasErrorsAndUpdateInventory(result, inventory)) return;
Log.d(TAG, "Query details was successful.");
List<SkuDetails> skuList = inventory.getAllProducts();
JSONObject jResponse = new JSONObject();
// Convert the java list to json
JSONArray jsonSkuList = new JSONArray();
try {
for (SkuDetails sku : skuList) {
Log.d(TAG, "SKUDetails: Title: "+sku.getTitle());
JSONObject jsonSku = new JSONObject();
jsonSku.put(sku.getSku(), sku.toJson());
skus.remove(sku.getSku());
jsonSkuList.put(jsonSku);
}
jResponse.put("validProducts", jsonSkuList);
JSONArray invalidSkus = new JSONArray();
for (String invalidSku: skus) {
invalidSkus.put(invalidSku);
}
jResponse.put("invalidProducts", invalidSkus);
} catch (JSONException e) {
callbackContext.error(e.getMessage());
}
callbackContext.success(jResponse);
}
};
public void onDestroy()
{
// very important:
Log.d(TAG, "Destroying helper.");
if (mHelper != null) {
mHelper.dispose();
mHelper = null;
}
}
@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 IABUtil.");
}
}
}