Version 3 of the Billing API removed the distinction between consumable and 
non-consumable products 
<https://developer.android.com/google/play/billing/api.html#consume>. Both 
have been combined into a new type called "managed" and behave somewhat 
like a hybrid: The app needs to actively call a method to "consume" the 
items. If that is never done for a set of skus, those items basically 
behave as if they were non-consumable.


The documentation 
<https://developer.android.com/google/play/billing/api.html#managingconsumables>
 describes 
the intended purchase flow as follows:

   1. Launch a purchase flow with a getBuyIntent call.
   2. Get a response Bundle from Google Play indicating if the purchase 
   completed successfully.
   3. If the purchase was successful, consume the purchase by making a 
   consumePurchase call.
   4. Get a response code from Google Play indicating if the consumption 
   completed successfully.
   5. If the consumption was successful, provision the product in your 
   application.
   
I see two problems with this approach. One is fairly obvious and more a 
"bug" in the documentation than the API, but the other is rather subtle and 
I still haven't figured out how to best handle it. Let's start with the 
obvious one for completeness:



*Problem 1: Lost purchases on single device:*


The docs say that an app should call getPurchases every time it is launched 
to "check if the user owns any outstanding consumable in-app products". If 
so, the app should consume these and provision the associated item. This 
covers the case where the purchase flow is interrupted after the purchase 
is completed, but before the item is consumed (i.e. around step 2).


But what if the purchase flow is interrupted between step 4 and 5? I.e. the 
app has successfully consumed the purchase but it got killed (phone call 
came in and there wasn't enough memory around, battery died, crash, 
whatever) before it had a chance to provision the product to the user. In 
such a case, the purchase will no longer be included in getPurchases and 
basically the user never receives what he paid for *(insert angry support 
email and one-star review here)*...


Luckily this problem is fairly easy to fix by introducing a "journal" (like 
in a file system <https://en.wikipedia.org/wiki/Journaling_file_system>) to 
change the purchase flow to something more like this (Steps 1 and 2 same as 
above):

   1. Launch a purchase flow with a getBuyIntent call.
   2. Get a response Bundle from Google Play indicating if the purchase 
   completed successfully.
   3. If the purchase was successful, make entry into journal saying 
   "increase coins from 300 to 400 once purchase *<order-id here>* is 
   successfully consumed."
   4. After journal entry is confirmed, consume the purchase by making a 
   consumePurchase call.
   5. Get a response code from Google Play indicating if the consumption 
   completed successfully.
   6. If the consumption was successful, provision the product in your 
   application.
   7. When provisioning is confirmed, change journal entry to "purchase 
*<order-id 
   here>* completed".
   8. Then, every time the app starts, it shouldn't just check getPurchases, 
   but also the journal. If there is an entry there for an incomplete purchase 
   that wasn't reported by getPurchases, continue at step 6. If a later 
   getPurchase should ever return that order ID as owned again (e.g. if the 
   consumption failed after all), simply ignore the transaction if the journal 
   lists this order ID as complete.
   
This *should* fix problem 1, but please do let me know if there are any 
flaws in this approach.



*Problem 2: Issues when multiple devices are involved:*


Let's say a user owns two devices (a phone and a tablet, for example) with 
the same account on both.


He (or she - to be implied from now on) could try to purchase more coins on 
his *phone* and the app could get killed after the purchase completed, but 
before it is consumed. Now, if he opens the app on his *tablet* next, 
getPurchases will report the product as owned.


The app on the tablet will have to assume that the purchase was initiated 
there and that it died before the journal entry was created, so it will 
create the journal entry, consume the product, and provision the coins.


If the phone app died before it had a chance to make the journal entry, the 
coins will never be provisioned on the phone *(insert angry support email 
and one-star review here)*. And if the phone app died after the journal 
entry was created, the coins will *also* be provisioned on the phone, 
basically giving the user a purchase for free on the tablet *(insert lost 
revenue here)*.


One way around this is to add some unique install or device ID as a payload 
to the purchase to check whether the purchase was meant for this device. 
Then, the tablet can simply ignore the purchase and only the phone will 
ever credit the coins and consume the item.


*BUT:* Since the sku is still in the user's possession at this point, the 
Play Store will not allow the user to buy another copy, so basically, until 
the user launches the app again on his phone to complete the pending 
transaction, he will not be able to purchase any more virtual coins on the 
tablet *(insert angry support email, one-star review, and lost revenue 
here)*.


Is there an elegant way to handle this scenario? The only solutions I can 
think of are:

   - Show a message to the user to please launch the app on the other 
   device first (yuck!)
   - or add multiple skus for the same consumable item (should work, but 
   still yuck!)
   
Is there a better way? Or am I maybe just fundamentally misunderstanding 
something and there really is no issue here? (I realize that the chances of 
this problem ever coming up are slim, but with a large enough user-base, 
"unlikely" eventually becomes "all-the-time".)

-- 
You received this message because you are subscribed to the Google Groups 
"Android Developers" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to android-developers+unsubscr...@googlegroups.com.
To post to this group, send email to android-developers@googlegroups.com.
Visit this group at https://groups.google.com/group/android-developers.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/android-developers/9a317774-17bc-4ef1-b23c-9bf302f85610%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to