With version 3 of the invoicing API, Google has removed the distinction between consumable and non-consumable products . Both were combined into a new type called “managed” and behave like a hybrid: your application needs to actively call the “consume” elements method. If this is never done for the skus set, these elements basically behave as if they were not consumed.
The documentation describes the estimated flow of purchases as follows:
- Start a purchase stream with a call to
getBuyIntent . - Get a
Bundle response on Google Play that indicates whether the purchase completed successfully. - If the purchase is successful, use the
consumePurchase purchase. - Get a response code from Google Play indicating whether the consumption was successful.
- If the consumption was successful, indicate the product in your application.
I see two problems with this approach. One of them is pretty obvious and more of a "mistake" in the documentation than the API, but the other is pretty subtle, and I still haven't figured out how to handle it better. Let's start with the obvious for completeness:
Problem 1: Losing a purchase on a single device:
The docs say the application should call getPurchases every time it starts to "check if the user owns any outstanding consumable products in the application." If so, the application should use them and provide a related element. This covers the case when the purchase flow is interrupted after the purchase is completed, but before the goods are consumed (i.e. around step 2).
But what if the flow of purchases is interrupted between steps 4 and 5? That is, the application successfully took advantage of the purchase, but it was killed (a phone call entered, but there was not enough memory, the battery worked, failure, etc.) before it had the opportunity to provide the product to the user. In this case, the purchase will no longer be included in getPurchases , and basically the user never gets what he paid for (insert an angry support email and a one-star review here) ...
Fortunately, this problem is quite easy to fix by entering a "log" ( as in the file system ) to change the purchase stream to something more (steps 1 and 2, as described above):
If the purchase was successful, enter a journal entry that says: "Increase coins from 300 to 400 as soon as the purchase <order-id here> is successfully used."
After confirming the journal entry, buy the purchase by calling consumePurchase .
- Get a response code from Google Play indicating whether the consumption was successful.
- If the consumption was successful, indicate the product in your application.
- When the setting is confirmed, change the journal entry to "purchase <order-id here> completed".
Then, every time the application starts, it should not only check getPurchases , but also the log. If there is some record for an incomplete purchase that was not reported by getPurchases , go to step 6. If a later getPurchase should ever return this order identifier as belonging again (for example, if consumption failed in the end) just ignore transaction if the journal lists this order ID as completed.
This should fix problem 1, but please let me know if you find any flaws in this approach.
Problem 2: Problems associated with multiple devices:
Say a user owns two devices (for example, a phone and a tablet) with the same account on both.
He (or she - implied from now on) can try to buy more coins on his phone , and the application can be killed after the purchase is completed, but before its consumption. Now, if he opens the application on his tablet further, getPurchases will report the product as belonging.
The application on the tablet will have to assume that the purchase was started there and that he died before the journal entry was created, so she will create a journal entry, go to the product and provide the coins.
If the phone application died before he had the opportunity to make a journal entry, coins would never be provided on the phone (insert an angry support email and a one-star review here). And if the phone application died after creating a journal entry, coins will also be provided on the phone, mainly giving the user a free purchase on the tablet (insert the lost income here).
One way is to add a unique setting or device identifier as a payload for the purchase, to check if the purchase was intended for that device. Then the tablet can simply ignore the purchase, and only the phone will ever credit coins and consume goods.
BUT: Since sku is still at the user's disposal at this stage, the Play Store will not allow the user to buy another copy, so basically until the user starts the phone application again to complete the pending transaction, he will not be able to purchase more virtual coins on your tablet (insert an angry support email, one-star review and lost revenue here).
Is there an elegant way to handle this scenario? The only solutions I can come up with are:
- Show a message to the user to first run the application on another device (yuck!)
- or add multiple skus for the same consumable item (should work, but still yuck!)
Is there a better way? Or maybe I just fundamentally misunderstand something, and here really is not a problem? (I understand that the chances of this problem ever appearing, but subtle, but with a fairly large user base, are "unlikely" to eventually become "all the time.")