In-App Billing: “Query items available for purchas

2019-03-13 11:57发布

问题:

I am trying to implement in-app-billing for my app.
I am following the implementation used in google's TriviaDrive sample app, and the relevant documentation on the Developer website.
My code is working as expected but when I try to "Query Items Available for Purchase", the resulting Inventory object contains 0 objects, even though I have created a product.

I have created a Managed Product with the id paid_version using the Google Play Developer Console, as shown in the image below:

The documentation indicates that "To retrieve the product details, call queryInventoryAsync(boolean, List, QueryInventoryFinishedListener) on your IabHelper instance."

In my own code I call
mHelper.queryInventoryAsync(true, iabItemSkus, mQueryFinishedListener)
where:
mHelper is my IabHelper instance
iabItemSkus is a List containing a single item with the value "paid_version"
mQueryFinishedListener is my listener defined below.

IabHelper.QueryInventoryFinishedListener mQueryFinishedListener = new IabHelper.QueryInventoryFinishedListener() {
        @Override
        public void onQueryInventoryFinished(IabResult result, Inventory inv) {
            if (result.isFailure()) {
                Log.d(TAG, "Querying Inventory Failed: " + result);
                return;
            }

            Log.d(TAG, "Title: " + inv.getSkuDetails(SKU_PAID).getTitle());
            Log.d(TAG, "Description: " + inv.getSkuDetails(SKU_PAID).getDescription());
            Log.d(TAG, "Price = " + inv.getSkuDetails(SKU_PAID).getPrice());
        }
    };

But on debugging I can see that the Inventory object passed back in the QueryInventoryFinishedListener contains 0 items, and so calls like inv.getSkuDetails(SKU_PAID).getTitle() give a null pointer exception.

I can't work out where I'm going wrong. I was expecting the Inventory object to contain the details for my paid_version in-app product.

Below are just the parts of my code and LogCat I think are relevant to this problem (trying to avoid giving you code overload!), but if more detail on some other part of the code would be helpful, let me know.

From my activity:

...
private static final String SKU_PAID = "paid_version";
private static final String TAG = "MyActivity";
private IabHelper mHelper;
...

IabHelper.QueryInventoryFinishedListener mQueryFinishedListener = new IabHelper.QueryInventoryFinishedListener() {
    @Override
    public void onQueryInventoryFinished(IabResult result, Inventory inv) {
        if (result.isFailure()) {
            Log.d(TAG, "Querying Inventory Failed: " + result);
            return;
        }

        Log.d(TAG, "Title: " + inv.getSkuDetails(SKU_PAID).getTitle()); // <-- Line 266 of MyActivity.java
        Log.d(TAG, "Description: " + inv.getSkuDetails(SKU_PAID).getDescription());
        Log.d(TAG, "Price = " + inv.getSkuDetails(SKU_PAID).getPrice());
    }
};
...

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ...

    final List<String> iabItemSkus = new ArrayList<String>();
    iabItemSkus.add(SKU_PAID);
    // In App Billing
    String base64EncodedPublicKey = "... My Public Key ...";
    mHelper = new IabHelper(this, base64EncodedPublicKey);
    mHelper.enableDebugLogging(true);
    mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
       @Override
       public void onIabSetupFinished(IabResult result) {
           if (!result.isSuccess()) {
               Log.d(TAG, "Problem setting up In-app Billing: " + result);
           }
           // Have we been disposed of in the meantime? If so, quit.
           if (mHelper == null) return;

           // IAB is fully set up. Now, let's get list of available items
           Log.d(TAG, "Setup successful. Querying inventory.");
           mHelper.queryInventoryAsync(true, iabItemSkus, mQueryFinishedListener);
       }
    });
    ...
}

From my LogCat:

...
05-13 19:46:59.609  22390-22390/xxx.xxxxxx.xxxxxx D/IabHelper﹕ Starting in-app billing setup.
05-13 19:46:59.629  22390-22390/xxx.xxxxxx.xxxxxx D/IabHelper﹕ Billing service connected.
05-13 19:46:59.629  22390-22390/xxx.xxxxxx.xxxxxx D/IabHelper﹕ Checking for in-app billing 3 support.
05-13 19:46:59.629  22390-22390/xxx.xxxxxx.xxxxxx D/IabHelper﹕ In-app billing version 3 supported for xxx.xxxxxx.xxxxxx
05-13 19:46:59.639  22390-22390/xxx.xxxxxx.xxxxxx D/IabHelper﹕ Subscriptions AVAILABLE.
05-13 19:46:59.639  22390-22390/xxx.xxxxxx.xxxxxx D/MyActivity﹕ Setup successful. Querying inventory.
05-13 19:46:59.639  22390-22390/xxx.xxxxxx.xxxxxx D/IabHelper﹕ Starting async operation: refresh inventory
05-13 19:46:59.649  22390-22596/xxx.xxxxxx.xxxxxx D/IabHelper﹕ Querying owned items, item type: inapp
05-13 19:46:59.649  22390-22596/xxx.xxxxxx.xxxxxx D/IabHelper﹕ Package name: xxx.xxxxxx.xxxxxx
05-13 19:46:59.649  22390-22596/xxx.xxxxxx.xxxxxx D/IabHelper﹕ Calling getPurchases with continuation token: null
05-13 19:46:59.659  22390-22596/xxx.xxxxxx.xxxxxx D/IabHelper﹕ Owned items response: 0
05-13 19:46:59.659  22390-22596/xxx.xxxxxx.xxxxxx D/IabHelper﹕ Continuation token: null
05-13 19:46:59.659  22390-22596/xxx.xxxxxx.xxxxxx D/IabHelper﹕ Querying SKU details.
05-13 19:46:59.689  22390-22596/xxx.xxxxxx.xxxxxx D/IabHelper﹕ Querying owned items, item type: subs
05-13 19:46:59.689  22390-22596/xxx.xxxxxx.xxxxxx D/IabHelper﹕ Package name: xxx.xxxxxx.xxxxxx
05-13 19:46:59.689  22390-22596/xxx.xxxxxx.xxxxxx D/IabHelper﹕ Calling getPurchases with continuation token: null
05-13 19:46:59.699  22390-22596/xxx.xxxxxx.xxxxxx D/IabHelper﹕ Owned items response: 0
05-13 19:46:59.699  22390-22596/xxx.xxxxxx.xxxxxx D/IabHelper﹕ Continuation token: null
05-13 19:46:59.699  22390-22596/xxx.xxxxxx.xxxxxx D/IabHelper﹕ Querying SKU details.
05-13 19:46:59.829  22390-22596/xxx.xxxxxx.xxxxxx D/IabHelper﹕ Ending async operation: refresh inventory
05-13 19:46:59.829  22390-22390/xxx.xxxxxx.xxxxxx D/AndroidRuntime﹕ Shutting down VM
05-13 19:46:59.829  22390-22390/xxx.xxxxxx.xxxxxx W/dalvikvm﹕ threadid=1: thread exiting with uncaught exception (group=0x41b31ba8)
05-13 19:46:59.839  22390-22390/xxx.xxxxxx.xxxxxx E/AndroidRuntime﹕ FATAL EXCEPTION: main
    Process: xxx.xxxxxx.xxxxxx, PID: 22390
    java.lang.NullPointerException
            at xxx.xxxxxx.xxxxxx.MyActivity$1.onQueryInventoryFinished(MyActivity.java:266)
            at xxx.xxxxxx.xxxxxx.util.IabHelper$2$1.run(IabHelper.java:630)
            at android.os.Handler.handleCallback(Handler.java:733)
            at android.os.Handler.dispatchMessage(Handler.java:95)
            at android.os.Looper.loop(Looper.java:136)
            at android.app.ActivityThread.main(ActivityThread.java:5017)
            at java.lang.reflect.Method.invokeNative(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:515)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:779)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:595)
            at dalvik.system.NativeStart.main(Native Method)
...

PS: I've just updated my code to highlight which is line 266.


Additional

I was experiencing this problem 12 hours after uploading my APK, and as you can see from the LogCat, it indicates "In-app billing version 3 supported" for my app.
The problem persisted regardless of whether I set the Status of the IN-APP PRODUCT as active or inactive.

Now 24 hours later it magically decides to work correctly.

From this I can only determine that it was a problem with Google Play, not with my code.

回答1:

I'm in the same boat. I'm waiting to see what happens.

In google documentation http://developer.android.com/training/in-app-billing/test-iab-app.html

there is a warning, perhaps this is the problem:

Warning: It may take up to 2-3 hours after uploading the APK for Google Play to recognize your updated APK version. If you try to test your application before your uploaded APK is recognized by Google Play, your application will receive a ‘purchase cancelled’ response with an error message “This version of the application is not enabled for In-app Billing.”



回答2:

I'm having the same problem. If you look at the IabHelper code, I think the issue is this:

Inside queryInventory:

if (querySkuDetails) {
    r = querySkuDetails(ITEM_TYPE_INAPP, inv, moreItemSkus);
    if (r != BILLING_RESPONSE_RESULT_OK) {
        throw new IabException(r, "Error refreshing inventory (querying prices of items).");
    }
}

But then the first few lines of querySkuDetails:

int querySkuDetails(String itemType, Inventory inv, List<String> moreSkus)
        throws RemoteException, JSONException {
    logDebug("Querying SKU details.");
    ArrayList<String> skuList = new ArrayList<String>();
    skuList.addAll(inv.getAllOwnedSkus(itemType));

Notice the last line populates the skuList with all owned skus. Not all available ones.

So the answer is that the IabHelper doesn't really support querying available purchases.



回答3:

To get a list of available purchases with prices, you need to call queryInventoryAsync with the parameters - lists of shopping identifiers:

ArrayList<String> skuList = new ArrayList<String> ();
skuList.add("purchase1");
ArrayList<String> subsList = new ArrayList<String> ();
subsList.add("subscribe1");
mHelper.queryInventoryAsync(true, skuList, subsList, mGotInventoryListener);


回答4:

I have tried above solution but it won't work for me and i got the solution from my another code, so i just want to share so may other will get help. For google in-app code please check this google github project

The difference is call queryInventoryAsync() with some more parameter that you have to pass and make sure that below method is call inside onIabSetupFinished() after IabHelper successfully initialized.

runOnUiThread(new Runnable() {
     @Override
     public void run() {
        refreshItemList();
     }
});

Prepare skurequest

private void refreshItemList() {
    List<String> itemSku = new ArrayList<>();
    List<String> subSku = new ArrayList<>();
    subSku.add(AppConstant.InApp.SKU_ONE_ID);
    mHelper.queryInventoryAsync(true, itemSku, subSku, mQotInventoryListener);
}

Below is the listener where you receive your SkuDetails

IabHelper.QueryInventoryFinishedListener mQotInventoryListener = new IabHelper.QueryInventoryFinishedListener() {
        @Override
        public void onQueryInventoryFinished(IabResult result, Inventory inv) {
            try {
                Log.d(TAG, "mQotInventoryListener 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()) {
                    Log.e(TAG, "mQotInventoryListener Failed to query inventory: " + result);
                    return;
                }

                Log.d(TAG, "mQotInventoryListener Query inventory was successful.");
                try {
                    //Here you just pass SKU_ID that you want its detail
                    SkuDetails skuDetails = inv.getSkuDetails(AppConstant.InApp.SKU_ONE_ID);
                    if (skuDetails != null) {
                      Log.d(TAG, "skuDetails are received"); 
                    } else {
                        Log.e(TAG, "skuDetails are null");
                    }

                } catch (Exception e) {
                    e.printStackTrace();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    };


回答5:

In order to have In-App Billing working, follow these steps:

  • Add <uses-permission android:name="com.android.vending.BILLING" /> permission to the AndroidManifest.xml .
  • Create a signed APK and upload it to the developer console, as alpha, beta or production.
  • Publish the APK or create a google group and add an alpha list of testers.
  • You have to wait some hours in order to see the in-app purchases in your app


回答6:

Late to answer, I too ran into same problem. After reading whole documentation for 3 days, finally I found the culprit.

According to documentation:

Draft Apps are No Longer Supported.

Previously, you could publish a "draft" version of your app for testing. This functionality is no longer supported. Instead, there are two ways you can test how a pre-release app functions on the Google Play store:

You can publish an app to the alpha or beta distribution channels. This makes the app available on the Google Play store, but only to the testers you put on a "whitelist". In a few cases, you can test Google Play functionality with an unpublished app. For example, you can test an unpublished app's in-app billing support by using static responses, special reserved product IDs that always return a specific result (like "purchased" or "refunded").

Google should ease this process.