Unable to test In App Subscription - android

I couldn't find a way to test InApp Subscription with the test product ID by google i.e. private final String productID = "android.test.purchased"; // Test Product ID by Google
In the docs it is not written anywhere that InAPP subscription couldn't be tested with test product nor it is mentioned anywhere,how to test InApp subscription.
I have implemented my code following docs(InAppV3).
The doc says:
Implementing Subscriptions:
Launching a purchase flow for a subscription is similar to launching the purchase flow for a product, with the exception that the product type must be set to "subs". The purchase result is delivered to your Activity's onActivityResult method, exactly as in the case of in-app products.
and I have also implemented that properly.
My app is working if I replace "inapp" with "subs",i.e. it is working perfectly for products and not for subscriptions.
When I change "inapp" to "subs" then the purchase is returning:
09-24 14:01:12.943: I/(16929): isBillingSupported() - success : return 0
09-24 14:01:12.943: D/Finsky(2598): [281] InAppBillingUtils.getPreferredAccount: com.kgandroid.inappsubscriptiondemo: Account from first account - [MOn42QuZgF98vxJi0p3wAN3rfzQ]
09-24 14:01:12.943: I/(16929): getPurchases() - success return Bundle
09-24 14:01:12.943: I/(16929): getPurchases() - "RESPONSE_CODE" return 0
09-24 14:01:12.943: I/(16929): getPurchases() - "INAPP_PURCHASE_ITEM_LIST" return []
09-24 14:01:12.943: I/(16929): getPurchases() - "INAPP_PURCHASE_DATA_LIST" return []
09-24 14:01:12.943: I/(16929): getPurchases() - "INAPP_DATA_SIGNATURE" return null
09-24 14:01:12.943: I/(16929): getPurchases() - "INAPP_CONTINUATION_TOKEN" return null
As you can see no details for android.test.purchased is returning.The test inapp purchase dialog is also not opening.
The relevant purchase code(Though it is not related to the problem I guess):
void purchase()
{
if (!blnBind) return;
if (mService == null) return;
ArrayList<String> skuList = new ArrayList<String>();
skuList.add(productID);
Bundle querySkus = new Bundle();
querySkus.putStringArrayList("ITEM_ID_LIST", skuList);
Bundle skuDetails;
try {
skuDetails = mService.getSkuDetails(3, getPackageName(), "subs", querySkus);
System.out.println(skuDetails);
Toast.makeText(context, "getSkuDetails() - success return Bundle", Toast.LENGTH_SHORT).show();
Log.i(tag, "getSkuDetails() - success return Bundle");
} catch (RemoteException e) {
e.printStackTrace();
Toast.makeText(context, "getSkuDetails() - fail!", Toast.LENGTH_SHORT).show();
Log.w(tag, "getSkuDetails() - fail!");
return;
}
int response = skuDetails.getInt("RESPONSE_CODE");
Toast.makeText(context, "getSkuDetails() - \"RESPONSE_CODE\" return " + String.valueOf(response), Toast.LENGTH_SHORT).show();
Log.i(tag, "getSkuDetails() - \"RESPONSE_CODE\" return " + String.valueOf(response));
if (response != 0) return;
ArrayList<String> responseList = skuDetails.getStringArrayList("DETAILS_LIST");
Log.i(tag, "getSkuDetails() - \"DETAILS_LIST\" return " + responseList.toString());
if (responseList.size() == 0) return;
for (String thisResponse : responseList) {
try {
JSONObject object = new JSONObject(thisResponse);
String sku = object.getString("productId");
String title = object.getString("title");
String price = object.getString("price");
Log.i(tag, "getSkuDetails() - \"DETAILS_LIST\":\"productId\" return " + sku);
Log.i(tag, "getSkuDetails() - \"DETAILS_LIST\":\"title\" return " + title);
Log.i(tag, "getSkuDetails() - \"DETAILS_LIST\":\"price\" return " + price);
if (!sku.equals(productID)) continue;
Bundle buyIntentBundle = mService.getBuyIntent(3, getPackageName(), sku, "subs", "bGoa+V7g/yqDXvKRqq+JTFn4uQZbPiQJo4pf9RzJ");
Toast.makeText(context, "getBuyIntent() - success return Bundle", Toast.LENGTH_SHORT).show();
Log.i(tag, "getBuyIntent() - success return Bundle");
response = buyIntentBundle.getInt("RESPONSE_CODE");
//Toast.makeText(context, "getBuyIntent() - \"RESPONSE_CODE\" return " + String.valueOf(response), Toast.LENGTH_SHORT).show();
Log.i(tag, "getBuyIntent() - \"RESPONSE_CODE\" return " + String.valueOf(response));
if (response != 0) continue;
PendingIntent pendingIntent = buyIntentBundle.getParcelable("BUY_INTENT");
startIntentSenderForResult(pendingIntent.getIntentSender(), 1001, new Intent(), 0, 0, 0);
} catch (JSONException e) {
e.printStackTrace();
} catch (RemoteException e) {
e.printStackTrace();
//Toast.makeText(context, "getSkuDetails() - fail!", Toast.LENGTH_SHORT).show();
Log.w(tag, "getBuyIntent() - fail!");
} catch (SendIntentException e) {
e.printStackTrace();
}
}
}
Does subscription supports test purchases??
If not,how to test subscription??
If yes,why google is returning null??
Any related docs or links will be also helpful.

I've never tried testing subs using the Test Product ID ("android.test.purchased") and I'm not sure it would work as seems to be a Product ID not a Subscription ID. I use real subscription IDs (that I create on Google Play Developer Console), but I use an account other than my developer account (i.e. the one you use to publish the app).
If you go to https://play.google.com/apps/publish/ then click on Settings, you'll see a field called "Gmail accounts with testing access". You can type one or more gmail addresses for accounts with which you want to test your purchases. Any account added there that purchases a subscription will not be charged and subscriptions will last for 24 hours. (source: http://developer.android.com/google/play/billing/billing_testing.html and my own experience)
That works for me. Although I did seem to have found a bug: Trying to cancel an Android test subscription gives me a 500 response code
Good luck with your app!
Edit:
From Google Docs (http://developer.android.com/google/play/billing/billing_testing.html#billing-testing-test)
You cannot use your developer account to test the complete in-app
purchase process because Google payments does not let you buy items
from yourself.
Have you tried using another account?

Related

In-App Purchase Android,Buy same item over and over without consumption

Good day.I am implementing in-app purchase into android.The only items which shall be bough are coins.And by idea coins can be bought over and over.But as we know the google play in-app purchase keeps the item as purchased,and you can buy it only after consumption.So what is an best practice for this situation?Like i want to let user buy same coin pack over and over.What is the code for it?
This is the code i am using for in-app purchase flow.
private ServiceConnection serviceConnection = new ServiceConnection() {
#Override
public void onServiceDisconnected(ComponentName name) {
mService = null;
}
#Override
public void onServiceConnected(ComponentName name,
IBinder service) {
mService = IInAppBillingService.Stub.asInterface(service);
try {
Bundle ownedItems = mService.getPurchases(3, getPackageName(), "inapp", null);
Log.d("Fasfsafasfasfa", "onServiceConnected: "+ownedItems);
int response = ownedItems.getInt("RESPONSE_CODE");
Log.d("Fasfsafasfasfa", "onServiceConnected: "+response);
if (response == 0) {
ArrayList<String> ownedSkus =
ownedItems.getStringArrayList("INAPP_PURCHASE_ITEM_LIST");
ArrayList<String> purchaseDataList =
ownedItems.getStringArrayList("INAPP_PURCHASE_DATA_LIST");
ArrayList<String> signatureList =
ownedItems.getStringArrayList("INAPP_DATA_SIGNATURE_LIST");
String continuationToken =
ownedItems.getString("INAPP_CONTINUATION_TOKEN");
Log.d("Fasfsafasfasfa", "onServiceConnected: "+ownedSkus.size());
Log.d("Fasfsafasfasfa", "onServiceConnected: "+purchaseDataList.size());
Log.d("Fasfsafasfasfa", "onServiceConnected: "+signatureList.size());
Log.d("Fasfsafasfasfa", "onServiceConnected: "+continuationToken);
for (int i = 0; i < purchaseDataList.size(); ++i) {
String purchaseData = purchaseDataList.get(i);
String signature = signatureList.get(i);
String sku = ownedSkus.get(i);
// do something with this purchase information
// e.g. display the updated list of products owned by user
Log.d("Fasfsafasfasfa", "onServiceConnected: " + "purchased items are" + sku + " " + signature + " " + purchaseData);
}
// if continuationToken != null, call getPurchases again
// and pass in the token to retrieve more items
}
Bundle buyIntentBundle = mService.getBuyIntent(3, getPackageName(),
"small_pack", "inapp", "bGoa+V7g/yqDXvKRqq+JTFn4uQZbPiQJo4pf9RzJ");
PendingIntent pendingIntent = buyIntentBundle.getParcelable("BUY_INTENT");
startIntentSenderForResult(pendingIntent.getIntentSender(),
1001, new Intent(), Integer.valueOf(0), Integer.valueOf(0),
Integer.valueOf(0));
} catch (RemoteException e) {
e.printStackTrace();
} catch (IntentSender.SendIntentException e) {
e.printStackTrace();
}
}
};
I have noticed this issue because on very first purchase everything is going fine,and whenever i try to buy same sku named item again,i get the fatal exception like this
java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.IntentSender android.app.PendingIntent.getIntentSender()' on a null object reference
at ink.va.activities.BuyCoins$1.onServiceConnected(BuyCoins.java:103)
Which i can reckon means that i must first consume purchase and only after that again let user buy it..Am i right?Anyway who have a suggestion of this issue please?
Consume your item to make it available to be purchased again:
int response = mService.consumePurchase(3, getPackageName(), token);
Warning: Do not call the consumePurchase method on the main thread. Calling this method triggers a network request which could block your main thread. Instead, create a separate thread and call the consumePurchase method from inside that thread.
Consumable products can be purchased multiple times by same the user.
Non-consumable can be purchased only once.

InAppBilling v3 IabResult response code BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED

Following the docs on developer site , i have implemented InAppBilling v3 in my app recently . I have used the classes in the utils package provided in the TRIVIAL DRIVE sample.
The problem i am facing is if a user has purchased an in app product already on launching purchse flow again on another device the play store dialog shows ITEM ALREADY OWNED but the response code returned by IabResult does not match to the constant IabHelper.BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED . The response code returned is actually one of the error codes in IabHelper class (-1005 User cancelled).
I would really like to know how can i get the actual response code instead of error code. Any help would be appreciated.
Below is the code for callback
// Callback for when a purchase is finished
IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener =
new IabHelper.OnIabPurchaseFinishedListener() {
public void onIabPurchaseFinished(IabResult result, Purchase purchase) {
if (result.isFailure()) {
if (result.getResponse() ==
IabHelper.BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED) {
//already owned
boolean isPremium = true;
SharedPrefsUtils.setPremium(BaseActivity.this, isPremium);
EventBus.getDefault().post(new InAppBillingUiUpdateEvent(isPremium));
//setWaitScreen(false);
return;
}
//handle error
complain(result.getResponse() + " " + "Error purchasing: " + result);
//setWaitScreen(false);
return;
}
if (!verifyDeveloperPayload(purchase)) {
//corrupted
complain("Error purchasing. Authenticity verification failed.");
//setWaitScreen(false);
return;
}
//successful
if (purchase.getSku().equals(NO_ADS_PRODUCT_ID)) {
// bought the premium upgrade!
alert("Thank you for upgrading to premium!");
boolean isPremium = true;
SharedPrefsUtils.setPremium(BaseActivity.this, isPremium);
EventBus.getDefault().post(new InAppBillingUiUpdateEvent(isPremium));
//setWaitScreen(false);
}
}
};
I finally managed to find the problem in the IabHelper code , So here it goes whenever an Activity.RESULT_CANCELED result code is returned in handleActivityResult method the IabResult for all such cases is fixed with user cancelled (-1005) no matter what the reason is. So in order to get the correct actual response code replace the following code in handleActivityResult
else if (resultCode == Activity.RESULT_CANCELED) {
logDebug("Purchase canceled - Response: " + getResponseDesc(responseCode));
result = new IabResult(IABHELPER_USER_CANCELLED, "User canceled.");
if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, null);
}
with this
else if (resultCode == Activity.RESULT_CANCELED) {
logDebug("Purchase canceled - Response: " + getResponseDesc(responseCode));
result = new IabResult(responseCode, null);
if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, null);
}
hope it will saves someones time

Android In-app Purchase V3 Error: Authentication is required

I am implementing Google In-app Purchase V3 and followed all steps stated over here as well in official documentation here. I have uploaded my app in Google Playstore for Alpha Testing and I have downloaded that from playstore URL into my real device but it giving me error
Error
Authentication is required. You need to sign into your Google Account.
My code for In-app purchase is here:
public class BuyPointsFragment extends Fragment
//In app Billing variable start
// Debug tag, for logging
static final String TAG = "com.myApp";
// Does the user have the premium upgrade?
boolean mIsPremium = false;
// Does the user have an active subscription to the infinite gas plan?
boolean mSubscribedToInfiniteGas = false;
// SKUs for our products: the premium upgrade (non-consumable) and gas
// (consumable)
static final String SKU_PREMIUM = "premium";
static final String SKU_GAS = "gas";
// SKU for our subscription (infinite gas)
static final String SKU_INFINITE_GAS = "infinite_gas";
// (arbitrary) request code for the purchase flow
static final int RC_REQUEST = 10001;
// Graphics for the gas gauge
static int[] TANK_RES_IDS = {};
// How many units (1/4 tank is our unit) fill in the tank.
static final int TANK_MAX = 4;
// Current amount of gas in tank, in units
int mTank;
// The helper object
IabHelper mHelper;
//In app billing variable end
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
//inapp load game data
loadData();
String base64EncodedPublicKey = "Base64Key from publisher account";
// Some sanity checks to see if the developer (that's you!) really
// followed the
// instructions to run this sample (don't put these checks on your app!)
if (base64EncodedPublicKey.contains("CONSTRUCT_YOUR")) {
throw new RuntimeException(
"Please put your app's public key in MainActivity.java. See README.");
}
if (getActivity().getPackageName().startsWith("com.myApp.activity")) {
throw new RuntimeException(
"Please change the sample's package name! See README.");
}
// Create the helper, passing it our context and the public key to
// verify signatures with
Log.d(TAG, "Creating IAB helper.");
mHelper = new IabHelper(getActivity(), base64EncodedPublicKey);
// enable debug logging (for a production application, you should set
// this to false).
mHelper.enableDebugLogging(true);
// 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.
complain(getString(R.string.problem_setting_inapp_billing) + result);
return;
}
// Have we been disposed of in the meantime? If so, quit.
if (mHelper == null)
return;
// IAB is fully set up. Now, let's get an inventory of stuff we
// own.
Log.d(TAG, "Setup successful. Querying inventory.");
mHelper.queryInventoryAsync(mGotInventoryListener);
}
});
//In app billing code end here
}
//In app billing methods start here
public void inappCall(){
setWaitScreen(true);
Log.d(TAG, "Launching purchase flow for gas.");
/*
* 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 carefully generate this.
*/
String payload = "";
mHelper.launchPurchaseFlow(getActivity(), SKU_GAS, RC_REQUEST,
mPurchaseFinishedListener, payload);
}
// updates UI to reflect model
public void updateUi() {
// update the car color to reflect premium status or lack thereof
// ((ImageView)findViewById(R.id.free_or_premium)).setImageResource(mIsPremium
// ? R.drawable.premium : R.drawable.free);
// "Upgrade" button is only visible if the user is not premium
// findViewById(R.id.upgrade_button).setVisibility(mIsPremium ?
// View.GONE : View.VISIBLE);
// "Get infinite gas" button is only visible if the user is not
// subscribed yet
// (R.id.infinite_gas_button).setVisibility(mSubscribedToInfiniteGas ?
// View.GONE : View.VISIBLE);
// update gas gauge to reflect tank status
if (mSubscribedToInfiniteGas) {
// ((ImageView)findViewById(R.id.gas_gauge)).setImageResource(R.drawable.gas_inf);
} else {
int index = mTank >= TANK_RES_IDS.length ? TANK_RES_IDS.length - 1
: mTank;
// ((ImageView)findViewById(R.id.gas_gauge)).setImageResource(TANK_RES_IDS[index]);
}
}
// Enables or disables the "please wait" screen.
void setWaitScreen(boolean set) {
// findViewById(R.id.screen_main).setVisibility(set ? View.GONE :
// View.VISIBLE);
// findViewById(R.id.screen_wait).setVisibility(set ? View.VISIBLE :
// View.GONE);
}
void complain(String message) {
Log.e(TAG, "**** TrivialDrive Error: " + message);
alert("Error: " + message);
}
void alert(String message) {
AlertDialog.Builder bld = new AlertDialog.Builder(getActivity());
bld.setMessage(message);
bld.setNeutralButton("OK", null);
Log.d(TAG, "Showing alert dialog: " + message);
bld.create().show();
}
void saveData() {
/*
* WARNING: on a real application, we recommend you save data in a
* secure way to prevent tampering. For simplicity in this sample, we
* simply store the data using a SharedPreferences.
*/
SharedPreferences.Editor spe = getActivity().getPreferences(getActivity().MODE_PRIVATE).edit();
spe.putInt("tank", mTank);
spe.commit();
Log.d(TAG, "Saved data: tank = " + String.valueOf(mTank));
}
void loadData() {
SharedPreferences sp = getActivity().getPreferences(getActivity().MODE_PRIVATE);
mTank = sp.getInt("tank", 2);
Log.d(TAG, "Loaded data: tank = " + String.valueOf(mTank));
}
// We're being destroyed. It's important to dispose of the helper here!
#Override
public void onDestroy() {
super.onDestroy();
// very important:
Log.d(TAG, "Destroying helper.");
if (mHelper != null) {
mHelper.dispose();
mHelper = null;
}
}
// 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, "Query inventory finished.");
// Have we been disposed of in the meantime? If so, quit.
if (mHelper == null)
return;
// Is it a failure?
if (result.isFailure()) {
complain(getString(R.string.failed_to_query_inventory) + result);
return;
}
Log.d(TAG, "Query inventory was successful.");
/*
* Check for items we own. Notice that for each purchase, we check
* the developer payload to see if it's correct! See
* verifyDeveloperPayload().
*/
// Do we have the premium upgrade?
Purchase premiumPurchase = inventory.getPurchase(SKU_PREMIUM);
mIsPremium = (premiumPurchase != null && verifyDeveloperPayload(premiumPurchase));
Log.d(TAG, "User is " + (mIsPremium ? "PREMIUM" : "NOT PREMIUM"));
// Do we have the infinite gas plan?
Purchase infiniteGasPurchase = inventory
.getPurchase(SKU_INFINITE_GAS);
mSubscribedToInfiniteGas = (infiniteGasPurchase != null && verifyDeveloperPayload(infiniteGasPurchase));
Log.d(TAG, "User "
+ (mSubscribedToInfiniteGas ? "HAS" : "DOES NOT HAVE")
+ " infinite gas subscription.");
if (mSubscribedToInfiniteGas)
mTank = TANK_MAX;
// Check for gas delivery -- if we own gas, we should fill up the
// tank immediately
Purchase gasPurchase = inventory.getPurchase(SKU_GAS);
if (gasPurchase != null && verifyDeveloperPayload(gasPurchase)) {
Log.d(TAG, "We have gas. Consuming it.");
mHelper.consumeAsync(inventory.getPurchase(SKU_GAS),
mConsumeFinishedListener);
return;
}
updateUi();
setWaitScreen(false);
Log.d(TAG, "Initial inventory query finished; enabling main UI.");
}
};
/** Verifies the developer payload of a purchase. */
boolean verifyDeveloperPayload(Purchase p) {
String payload = p.getDeveloperPayload();
/*
* 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.
*/
return true;
}
// Callback for when a purchase is finished
IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener = 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)
return;
if (result.isFailure()) {
complain(getString(R.string.error_purchase) + result);
setWaitScreen(false);
return;
}
if (!verifyDeveloperPayload(purchase)) {
complain(getString(R.string.error_purchase_authenitcity_failed));
setWaitScreen(false);
return;
}
Log.d(TAG, "Purchase successful.");
if (purchase.getSku().equals(SKU_GAS)) {
// bought 1/4 tank of gas. So consume it.
Log.d(TAG, "Purchase is gas. Starting gas consumption.");
mHelper.consumeAsync(purchase, mConsumeFinishedListener);
} else if (purchase.getSku().equals(SKU_PREMIUM)) {
// bought the premium upgrade!
Log.d(TAG, "Purchase is premium upgrade. Congratulating user.");
alert(getString(R.string.thank_you_updgraing_premium));
mIsPremium = true;
updateUi();
setWaitScreen(false);
} else if (purchase.getSku().equals(SKU_INFINITE_GAS)) {
// bought the infinite gas subscription
Log.d(TAG, "Infinite gas subscription purchased.");
alert("Thank you for subscribing to infinite gas!");
mSubscribedToInfiniteGas = true;
mTank = TANK_MAX;
updateUi();
setWaitScreen(false);
}
}
};
// Called when consumption is complete
IabHelper.OnConsumeFinishedListener mConsumeFinishedListener = new IabHelper.OnConsumeFinishedListener() {
public void onConsumeFinished(Purchase purchase, IabResult result) {
Log.d(TAG, "Consumption finished. Purchase: " + purchase
+ ", result: " + result);
// if we were disposed of in the meantime, quit.
if (mHelper == null)
return;
// 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, which in our case means filling the gas
// tank a bit
Log.d(TAG, "Consumption successful. Provisioning.");
mTank = mTank == TANK_MAX ? TANK_MAX : mTank + 1;
saveData();
alert("You filled 1/4 tank. Your tank is now "
+ String.valueOf(mTank) + "/4 full!");
} else {
complain("Error while consuming: " + result);
}
updateUi();
setWaitScreen(false);
Log.d(TAG, "End consumption flow.");
}
};
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
Log.d(TAG, "onActivityResult(" + requestCode + "," + resultCode + ","
+ data);
if (mHelper == null)
return;
// 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.");
}
}
//In app billing method end here
My products are Managed products in developer account In-app Products
EDIT:
When I use android.test.purchased as a SKU then it works fine and as I change my sku with my product_id then it giving me Error Authentication is required. You need to sign into your Google Account.
Please make sure your product id on playstore for in app purchase which is "test_product" you should use the same sku in your code. And if you change the sku from your application, then all that possible sku names must exists on PlayStore as in-app Products. I once run into this problem and the reason was my SKU item was not exists on Google Playstore in-app products i just add it on Playstore and it was resolved.
Android IAB Error - Authentication required
It seems that you need to publish the APK. I stuck with this problem too.
It is a bit of a longshot, but your "Google Play store" app could be malfunctioning.
I have a report from a customer that he cannot make an In-App purchase and he is getting this same error. I also know that we are still getting purchases. So I don't think you necessarily configured anything wrong. IAB v3 could simply be malfunctioning with your "Google Play Store" app.
Try going to Settings open the Application manager and choose the "Google Play store" application in the list, and click on "Uninstall updates" button which appears there. Next try to do the purchase again. If your old Google Play application supports In-App billing v3 it will work. If it doesn't, try to update the "Google Play store" app again, perhaps you will get a newer/different version of the store app and you will be able to make the purchase.
As an alternative you can try and configure a second test device with a different Google account... or ask a friend to join a beta test group and make a test purchase.
I am not really happy with IAB v3. It feels a little clunky. V2 had fewer features but felt solid.

In App Billing Error: Item already owned (BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED)

I am working on Android Product which supports in app purchase and have large quantity of users doing in app purchases , out of all users some users are facing "BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED" error and the response returned is 7 .
I am calling consume call after every purchase being awarded , and also verifying my inventory when the InApp is setup on the start of application and check for any owned item and call the consume on it.
Any recommendation's to solve the issue .
See here. Google says that you MUST consume managed inapp items. If you don't, the user cannot purchase another one. After successful purchase, use:
int response = mService.consumePurchase(3, getPackageName(), token);
You get the token from the purchaseData JSON object:
final String token = jo.getString("purchaseToken");
But if you for some reason did not consume a purchase, you are stuck.
I ran into the same problem because I upgraded to iap api v3 and in v2 this was not a problem.
When you get this error, try to consume all purchases of the given productId. Or just consume everything purchased during setup of the service. Note that you might want to actually provision the purchase after consumePurchase() successfully returns depending on the semantics of your application.
Call the following code after the service is connected (in onServiceConnected()) and of course execute it in background:
String continuationToken="";
boolean hasMorePurchases=false;
do {
try {
Bundle purchases = mService.getPurchases(3, activity.getPackageName(), "inapp", continuationToken);
int response = purchases.getInt("RESPONSE_CODE");
if (response == 0) {
continuationToken = purchases.getString("INAPP_CONTINUATION_TOKEN");
if(!TextUtils.isEmpty(continuationToken)) hasMorePurchases=true;
final ArrayList<String> purchaseDataList = purchases.getStringArrayList("INAPP_PURCHASE_DATA_LIST");
for(String purchaseJSON : purchaseDataList) {
JSONObject object = new JSONObject(purchaseJSON);
String productId = object.getString("productId");
String orderId = object.getString("orderId");
String purchaseToken = object.getString("purchaseToken");
Log.i(getClass().getSimpleName(),"consuming purchase of " + productId + ", orderId " + orderId);
mService.consumePurchase(3, activity.getPackageName(), purchaseToken);
}
} else {
Log.e(getClass().getSimpleName(), "could not get purchases: " + response);
}
} catch (RemoteException e) {
Log.e(getClass().getSimpleName(), "RemoteException during getPurchases:", e);
} catch (JSONException e) {
Log.e(getClass().getSimpleName(), "JSONException during getSkuDetails:", e);
}
} while(hasMorePurchases);
Note that each call to getPurchases returns a maximum of 700 purchases, so you need to use the continuation token to get more.
You can of course just use this code if you get the ALREADY_OWNED error code and only for the productId involved. Afterwards, try start the purchase again.

How to use nonce with Google in app billing api V3

I am trying to implement in app purchase in my android application. I am using In app billing API v3 for implementing IAP (in app purchase). For reference I am following Google provided trivialdrive sample. In launchPurchaseFlow method
public void launchPurchaseFlow(Activity act, String sku, String itemType, int requestCode,
OnIabPurchaseFinishedListener listener, String extraData) {
checkSetupDone("launchPurchaseFlow");
flagStartAsync("launchPurchaseFlow");
IabResult result;
if (itemType.equals(ITEM_TYPE_SUBS) && !mSubscriptionsSupported) {
IabResult r = new IabResult(IABHELPER_SUBSCRIPTIONS_NOT_AVAILABLE,
"Subscriptions are not available.");
if (listener != null) listener.onIabPurchaseFinished(r, null);
return;
}
try {
logDebug("Constructing buy intent for " + sku + ", item type: " + itemType);
Bundle buyIntentBundle = mService.getBuyIntent(3, mContext.getPackageName(), sku, itemType, extraData);
int response = getResponseCodeFromBundle(buyIntentBundle);
if (response != BILLING_RESPONSE_RESULT_OK) {
logError("Unable to buy item, Error response: " + getResponseDesc(response));
result = new IabResult(response, "Unable to buy item");
if (listener != null) listener.onIabPurchaseFinished(result, null);
return;
}
PendingIntent pendingIntent = buyIntentBundle.getParcelable(RESPONSE_BUY_INTENT);
logDebug("Launching buy intent for " + sku + ". Request code: " + requestCode);
mRequestCode = requestCode;
mPurchaseListener = listener;
mPurchasingItemType = itemType;
act.startIntentSenderForResult(pendingIntent.getIntentSender(),
requestCode, new Intent(),
Integer.valueOf(0), Integer.valueOf(0),
Integer.valueOf(0));
}
catch (SendIntentException e) {
logError("SendIntentException while launching purchase flow for sku " + sku);
e.printStackTrace();
result = new IabResult(IABHELPER_SEND_INTENT_FAILED, "Failed to send intent.");
if (listener != null) listener.onIabPurchaseFinished(result, null);
}
catch (RemoteException e) {
logError("RemoteException while launching purchase flow for sku " + sku);
e.printStackTrace();
result = new IabResult(IABHELPER_REMOTE_EXCEPTION, "Remote exception while starting purchase flow");
if (listener != null) listener.onIabPurchaseFinished(result, null);
}
}
In the above code getBuyIntent does not expect nonce, we have extradata in which we pass developerpayload (specific to each purchase item).
Here I am not able to figure out how to pass nonce as it was passed in API V2 and received as response on successful purchase. Is there no need of nonce in V3?
Thanks
When using IAB API v3, simply use the original JSON response you receive from Google Play and perform an OpenSSL verification on it, using the signature and public RSA.
It's best that you perform this check through your own API, as bundling the public RSA key inside your application is unsafe. Here's a PHP sample for this OpenSSL verification.
As that sample illustrates, it simply takes the parameters from the request: the response data and the signature and verifies it using the public key.
You only need to tamper with the original JSON data inside your app and on your API for purposes related to figuring out what the user bought, not for verifying the validity of the purchase itself.
So in short: don't worry about the nonce. IAB API v3 dropped it and you shouldn't concern yourself with its absence.
APIv3 returns orderId, which can be used as nonce to avoid replay attacks.

Categories

Resources