Consumable In-App Purchase in Android

15,095

It's happened because you are not properly added Consume listener, first let's know why it is required consume finish listener(and queryInventoryAsync) called when your item is purchased, and Google play store registered that your item is purchased successfully, so that Google play allow user next time to purchase same product from the same google account.

Make sure you have put all this method properly in your activity:

Start Setup

// 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("Problem setting up in-app billing: " + result);
                    return;
                }

                // Hooray, 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);
            }
        });

on Activity Result:

@Override
    protected 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.");
        }
    }

Query Inventory Finish listener

// 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.");
            if (result.isFailure()) {
                complain("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().
             */

            // // 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;
            }

            // update UI
            // updateUi();
            // setWaitScreen(false);
            Log.d(TAG, "Initial inventory query finished; enabling main UI.");
        }
    };

Consume Finish listerner

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

            // 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.");
        }
    };

on Purchase finish listener

// 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(mHelper == null)
                         return;

            if (result.isFailure()) {
                complain("Error purchasing: " + result);
                // setWaitScreen(false);
                return;
            }
            if (!verifyDeveloperPayload(purchase)) {
                complain("Error purchasing. Authenticity verification 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);
            }

        }
    };

EDIT

also make sure you have followed these links too,

Link1 & Link2

Hope it will help you.

Share:
15,095
Chintan
Author by

Chintan

Updated on June 29, 2022

Comments

  • Chintan
    Chintan almost 2 years

    I have one game kind of application. I have gone through a lot of surf but I haven't get any satisfied solution. Here, user can buy coin packs more than once. I have some some problems in my code so user can buy only once. I have read documentation about consumable IAP( In App Purchase ) and still the same problem. If I make consumablePurchase() call, it gives BILLING_RESPONSE_RESULT_DEVELOPER_ERROR (ResponseCode 5).

    Steps :

    1) Call purchasePackage("android.test.iap.500coins")

    public void purchasePackage(String product_id) {
    
        try {
    
            Log.i(TAG, "product name : " + product_id);
            package_name = product_id;
    
            Bundle buyIntentBundle = mService
                    .getBuyIntent(3, getPackageName(), product_id, "inapp",
                            "C890B68423F8EA57F3ED38C3DCC816D7E389F4Cdc4961C23540dadC866B8CFFC5");
            Log.i(TAG,
                    "buy intent response :  "
                            + buyIntentBundle
                                    .getInt("BILLING_RESPONSE_RESULT_OK"));
            if (buyIntentBundle.getInt("BILLING_RESPONSE_RESULT_OK") == 0) {
                Log.i(TAG, "buyIntentBundle created");
                PendingIntent pendingIntent = buyIntentBundle
                        .getParcelable("BUY_INTENT");
                Log.i(TAG, "pendingIntent created");
                startIntentSenderForResult(pendingIntent.getIntentSender(),
                        1101, new Intent(), Integer.valueOf(0),
                        Integer.valueOf(0), Integer.valueOf(0));
                Log.i(TAG, "startIntentSenderForResult started");
            } else
                Log.i(TAG, "getBuyIntent response not ok");
        } catch (RemoteException e) {
            // TODO: handle exception
            Log.e(TAG, "RemoteException : " + e.getMessage());
        } catch (Exception e) {
            // TODO: handle exception
            Log.e(TAG, "Error in buyStructure : " + e.getMessage());
        }
    }
    

    2) get response in onActivityResult(int requestCode, int resultCode, Intent data) and make consumablePurchase() after getting response of purchasePackage()

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        // TODO Auto-generated method stub
    
        Log.i(TAG, "requestCode : " + requestCode + " :resultCode : "
                + resultCode);
        if (data != null && requestCode == 1101) {
    
            int responseCode = data.getIntExtra("RESPONSE_CODE", -1);
            String purchaseData = data.getStringExtra("INAPP_PURCHASE_DATA");
            String dataSignature = data.getStringExtra("INAPP_DATA_SIGNATURE");
    
            Log.i(TAG, "responseCode : " + responseCode);
            if (resultCode == RESULT_OK) {
                try {
                    switch (responseCode) {
    
                    case 0:
                        /*new Thread() {
                            @Override
                            public void run() {
                                mHandler.sendEmptyMessage(purchaseStart);
                                StartupSync purchaseSync = new StartupSync(
                                        InAppActivity.this, mHandler);
                                purchaseSync.purchasePackage(package_name);
                                mHandler.sendEmptyMessage(purchaseComplete);
                            }
                        }.start(); */
                        JSONObject jo = new JSONObject(purchaseData);
                        String sku = jo.getString("productId");
                        String purchaseToken = jo.getString("purchaseToken");
    
                        Log.i(TAG, "You have bought the " + sku
                                + ". Excellent choice,adventurer!");
    
                        int coins = Integer.parseInt(db.selectSettingsValue("coins"));
                        Log.i(TAG, "coins " + coins);
                        coins = coins + intIncCoins;
                        Log.i(TAG, "coins " + coins);
    
                        db.updateSettings("coins", coins + "");
                        Toast.makeText(
                                InAppActivity.this,
                                "Thank You !",
                                Toast.LENGTH_SHORT).show();
                        finish();
    
                        int response =  mService.consumePurchase(3, sku, purchaseToken);
                        Toast.makeText(
                                InAppActivity.this,
                                "Response : " + response ,
                                Toast.LENGTH_LONG).show();
    
                        break;
    
                    case 1:
                        Log.i(TAG, "BILLING_RESPONSE_RESULT_USER_CANCELED");
                        Toast.makeText(InAppActivity.this,
                                "User pressed back or canceled a dialog",
                                Toast.LENGTH_SHORT).show();
                        break;
                    case 3:
                        Log.i(TAG,
                                "BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE");
                        Toast.makeText(
                                InAppActivity.this,
                                "Billing API version is not supported for the type requested",
                                Toast.LENGTH_SHORT).show();
                        break;
                    case 4:
                        Log.i(TAG, "BILLING_RESPONSE_RESULT_ITEM_UNAVAILABLE");
                        Toast.makeText(
                                InAppActivity.this,
                                "Requested product is not available for purchase",
                                Toast.LENGTH_SHORT).show();
                        break;
                    case 5:
    
                        Log.i(TAG, "BILLING_RESPONSE_RESULT_DEVELOPER_ERROR");
                        Toast.makeText(
                                InAppActivity.this,
                                "Invalid arguments provided to the API. This error can also indicate that the application was not correctly signed or properly set up for In-app Billing in Google Play, or does not have the necessary permissions in its manifest",
                                Toast.LENGTH_SHORT).show();
                        break;
                    case 6:
    
                        Log.i(TAG, "BILLING_RESPONSE_RESULT_ERROR");
                        Toast.makeText(InAppActivity.this,
                                "Fatal error during the API action",
                                Toast.LENGTH_SHORT).show();
                        break;
                    case 7:
                        Log.i(TAG, "BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED");
                        Toast.makeText(
                                InAppActivity.this,
                                "Failure to purchase since item is already owned",
                                Toast.LENGTH_SHORT).show();
                                                break;
                    case 8:
                        Log.i(TAG, "BILLING_RESPONSE_RESULT_ITEM_NOT_OWNED");
                        Toast.makeText(InAppActivity.this,
                                "Failure to consume since item is not owned",
                                Toast.LENGTH_SHORT).show();
                        break;
                    }
    
                } catch (JSONException e) {
                    Log.i(TAG, "Failed to parse purchase data.");
                    e.printStackTrace();
                } catch (RemoteException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            } else if (resultCode == RESULT_CANCELED) {
                Toast.makeText(InAppActivity.this, "Purchase Failded",
                        Toast.LENGTH_SHORT).show();
            }
        } else if (resultCode == RESULT_CANCELED) {
            Toast.makeText(InAppActivity.this, "Purchase Canceled",
                    Toast.LENGTH_SHORT).show();
        }
        super.onActivityResult(requestCode, resultCode, data);
    }
    

    Even I have tried another way like getting an array of all purchased IAP and make them consumable. I have written code in splash activity. Printed log is here.

    Why does it give same error again and again ? That was a bug solved by google itself in March 2013.

    Any suggestions/advice acceptable!