I have a new android app in which I am adding in-app billing and I am tearing my hair out with frustration.
I have uploaded a signed APK and published to alpha. I created a set of in-app products and activated them all. I have created a new gmail account and defined them as a tester for the app on the app apk page. I have factory reset my android phone and initialised it with the new gmail account. I have entered the /apps/testing link in to chrome and signed up as a tester. I then downloaded and installed my app. Inside my app I asked for the in app products that were available and was shown the set i created above. I selected one to buy and went through the following purchase process. 1. Screen shows product to be purchased and price and requests press continue which i do 2. Screen shows payment methods and I select redeem code 3. Screen shows redeem your code and I enter one of the promotion codes I set up in the developer console earlier (not mentioned above - sorry) and press redeem 4. Screen shows product again, this time with price crossed out and offers option to add item which I select (very strange being asked to add again buy hey ho) 5. Screen shows item added 6. After a fews seconds screen shows Error you already own this item.
How can this be, this user did not exist before ten minutes ago and has only used this app once as described above.
I have seen many questions in stack overflow and elsewhere similar to this and tried everything, clearing google play store cache, clearing google play store data etc. This sequence described above is my latest attempt with a completely clean user on a completely clean phone.
I could upload my app code used but that misses the point, which is how can this gmail account already own an item when this gmail account have never purchased anything before from anyone. Surely this is a bug.
All clues very welcome as to how to proceed. Code now added, note this is a hybrid android app, with the user purchase decisions code in javascript/html and the in app actions in the wrapper code below
private void processCommand(JSONObject commandJSON) throws JSONException
{
String command = commandJSON.getString("method");
if ("GetInAppProducts".equals(command))
{
Log.d(TAG, "Querying Inventory");
InAppPurchaseSkuString = null ; // clear the purchased sku. Note this is tested in mConsumeFinishedListener
mHelper.queryInventoryAsync(true, itemSkus, new IabHelper.QueryInventoryFinishedListener()
{
@Override
public void onQueryInventoryFinished(IabResult iabResult, Inventory inventory)
{
InventoryRecord = inventory ;
if (iabResult.isFailure())
{
Log.d(TAG, "Query inventory failed");
SendEndItemsToApp ();
}
else
{
Log.d(TAG, "Query inventory was successful.");
InventoryCheckCount = 0 ; // seems that we cannot just fire off a whole lot of these checks at the same time, so do them in sequence
if (itemSkus.size()>0) { CheckForOwnedItems (); } else { SendEndItemsToApp (); }
}
}
});
}
else if ("BuyInAppProduct".equals(command))
{
JSONArray params = commandJSON.getJSONArray("parameters");
InAppPurchaseSkuString = params.getString(0);
Log.d(TAG, "User decision to purchase " + InAppPurchaseSkuString);
mHelper.launchPurchaseFlow( MainActivity.this, InAppPurchaseSkuString, InAppPurchaseActivityCode, mPurchaseFinishedListener, "mypurchasetoken"); // consider putting the user email address in the last field - need to get from app
};
}//end of ProcessCommand
public void CheckForOwnedItems ()
{
Log.d(TAG, "Pre Purchase Inventory Processing Started");
String sku = itemSkus.get(InventoryCheckCount);
if (InventoryRecord.getSkuDetails(sku) != null)
{
if (InventoryRecord.hasPurchase(sku))
{
consumeItem ();
}
else
{
SendItemToApp ();
InventoryCheckCount++;
if (InventoryCheckCount < itemSkus.size()) { CheckForOwnedItems (); } else { SendEndItemsToApp (); }
};
};
}//end of CheckForOwnedItems
public void SendItemToApp ()
{
String sku = itemSkus.get(InventoryCheckCount);
String priceString = InventoryRecord.getSkuDetails(sku).getPrice().replaceAll("[^\\d.]+", ""); // RegExp removes all characters except digits and periods
String infoString = "InAppProductDetails('" + sku + "','" + "dummy" + "','" + priceString + "');"; // dummy is a placeholder for product description which is not (yet?) used in the app
Log.d(TAG, infoString);
mWebView.evaluateJavascript (infoString, new ValueCallback<String>()
{
@Override
public void onReceiveValue(String s)
{
//Log.d(TAG,"Returned from InAppProductDetails:");
}
}
);
}
public void SendEndItemsToApp ()
{
String endString = "InAppProductsEnd();"; // name is a placeholder for now
Log.d(TAG, endString);
mWebView.evaluateJavascript(endString, new ValueCallback<String>()
{
@Override
public void onReceiveValue(String s)
{
//Log.d(TAG,"Returned from InAppProductsEnd:");
}
}
);
}
public void consumeItem()
{
Log.d(TAG,"Pre Purchase Inventory Query Started");
String sku = itemSkus.get(InventoryCheckCount);
mHelper.consumeAsync(InventoryRecord.getPurchase(sku), mConsumeFinishedListener);
}
IabHelper.OnConsumeFinishedListener mConsumeFinishedListener = new IabHelper.OnConsumeFinishedListener()
{
public void onConsumeFinished (Purchase purchase, IabResult result)
{
if (result.isSuccess())
{
Log.d(TAG, "Pre Purchase Consume Item Completed");
SendItemToApp ();
InventoryCheckCount++;
if (InventoryCheckCount < itemSkus.size()) { CheckForOwnedItems (); } else { SendEndItemsToApp (); }
}
else
{
Log.d(TAG,"Pre Purchase Consume Item Failed");
}
}
};
IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener = new IabHelper.OnIabPurchaseFinishedListener()
{
public void onIabPurchaseFinished (IabResult result, Purchase purchase)
{
if (result.isFailure())
{
Log.d(TAG,"Purchase Scenario Failed");
}
else if (purchase.getSku().equals(InAppPurchaseSkuString))
{
Log.d(TAG,"Purchase Scenario Completed");
String evalString = "InAppProductPurchased('" + InAppPurchaseSkuString + "');";
Log.d(TAG, evalString);
mWebView.evaluateJavascript (evalString, new ValueCallback<String>()
{
@Override
public void onReceiveValue(String s)
{
Log.d(TAG, "Returned from InAppProductPurchased:");
}
}
);
}
}
};