Testing In-App Billing Version 3 has been made unpredictable by the fact that the Google Play app buffers data from the Google Play servers, and the buffering process is not well-documented. In particular, if a purchase is made on one of a user's devices, it may not be immediately visible on a different device owned by that same user.
Recommended practice is for an app to check the inventory of all purchased products when it is started. But sometimes this check uses buffered data that is already stale due to updates made via the same app on a different device, or via Google Checkout (e.g., a refund or a canceled order).
What are the circumstances under which a change made to purchase data on the Google Play servers becomes available on a Google Play app running on a device that did not initiate that update?
Specifically, how can one ensure during testing that data returned by a check using queryInventoryAsync() (a method of the IabHelper class supplied with the TrivialDrive sample app) reflects what is presently on the Google Play servers, rather than being possibly stale buffered data?
Usually the BILLING_UNAVAILABLE error means that your Android device is running an unsupported version of Android or Play services. Other things to check when you get this error: Are you logged in to the correct Google Account on the device/emulator? Try logging out and logging back in.
In-app Billing is a Google Play service that provides checkout processing for in-app purchases. To use the service, your application sends a billing request for a specific in-app product.
The In-app Billing Version 3 API makes it easier for you to integrate In-app Billing into your applications. The features in this version include improved synchronous purchase flow, APIs to let you easily track ownership of consumable goods, and local caching of in-app purchase data.
Here is my own experience purchasing an item with an app running on a Nexus 7 tablet and then detecting that purchase using the same app running on a Nexus One phone. The testing described below was performed using a test account for an app that was uploaded in draft mode (not yet published). The test account was declared on the Developer Console for the draft app, and was the main account for both test devices.
The purchase made was of a non-consumable item. The purchase was made using a variant of the IabHelper class provided with the TrivialDrive sample app. The IabHelper method invoked to make the purchase was launchPurchaseFlow().
As soon as the purchase was made, the item was added to the list of purchased items returned to that same device when IabHelper's queryInventoryAsync() method was subsequently used.
However, a separate Nexus One device owned by the same account, when started, performed a call to queryInventoryAsync() but did not receive the purchased item in the inventory of purchased items returned using that method.
If, however, the Nexus One device was used to initiate a purchase of the same item using launchPurchaseFlow(), a message was returned (in a dialog that popped up in front of the display that would have been used to make the purchase), indicating that the item cannot be purchased because it is already owned. This occurred less than 15 minutes after the purchase was initiated from the Nexus 7, showing that the data was fairly promptly available on the Google Play servers, but was not automatically available on the Nexus One, until the attempt to re-purchase the item from the Nexus One device was initiated.
Following this abortive attempt to purchase the already-owned item, the item did appear on subsequent invocations of queryInventoryAsync() on the Nexus One. This suggests that the attempt to purchase the item triggered a synchronization of the Nexus One Google Play app's buffered data with the data available on the Google Play servers. This was not triggered by queryInventoryAsync() itself.
I tested a second scenario in which, instead of making an attempt to purchase the already-owned item from the Nexus One, I deleted the cache in the Google Play app. This did not initiate an update of the data returned by queryInventoryAsync(); that data remained unchanged.
I tested a third scenario in which, instead of making an attempt to purchase the already-owned item from the Nexus One, I simply powered down the Nexus One and then powered it up again. After this, when I ran the same app and it performed a queryInventoryAsync() request, the item that was purchased from the Nexus 7 was in fact visible in the returned list of purchased items.
I conclude from the above that the Google Play app is attempting to reduce the number of round trips that it makes to the Google Play servers by buffering purchases locally and only updating itself when specific triggers occur. I have identified two triggers as being 1) an attempt to purchase an already-owned item and 2) a reboot of the device on which the Google Play app is running. In particular, queryInventoryAsync() does not initiate such an update, and so may return stale data, as described above.
Knowing the above can make testing more efficient and less confusing, because it allows one to deliberately trigger updating of the buffered data from the data that is available on the Google Play servers.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With