I've not built an android app for in app purchases before. I've been doing a bunch of reading and it looks like, there's two kinds of products: managed product and subscription.
I don't want to pick the wrong one because it looks like some of these are permanent, so I want to get it right out of the gate. The docs say that managed products are one time and belong to the user forever and so, I think maybe I need a subscription.
Subscription, though, would indicate renewal and that isn't right either. I did find some documentation that says you can mark a purchase as consumed. Is that for subscriptions or managed products?
This is common enough that I'm sure lots of people have solved it, but searches for in-app products currencies returns information about conversion to other currencies.
I'm working through the docs and currently here: https://developer.android.com/training/in-app-billing/list-iab-products.html and why doesn't that talk about managed products and subscriptions?
I finally found https://developer.android.com/google/play/billing/api.html after much searching. In it it indicates that you can only consume managed products, so that seems to be what I want.
Related
My app uses google's billing API to retrieve subscription options configured in google's developer console. We have some subscription options configured as free trials.
So far we've been using the IInAppBillingService.getSkuDetails() method. This reference -
https://developer.android.com/google/play/billing/billing_reference.html#getSkuDetails - shows what kind of data you can retrieve data regarding the subscription item. But sadly it does not include the trialPeriod nor billingPeriod.
I stumbled upon this google play API https://developers.google.com/android-publisher/api-ref/inappproducts that shows you can query the in-app products and get a response with more details.
Is this information somehow available using the IInAppBillingService ?
There is currently no API that returns the info if a subscription is bought directly or through free trial.
Haven't tried it myself yet, but a workaround I could think of is to have different periods for an actual subscription and a trial period. Say have an actual subscription to have a minimum of 1-month subscription, then for a free-trial, have it as 1-week. (The difference in periods kinda gives the free trial a demo-like thing into it too)
With that done, you could call Purchases.subscriptions.get (or depending on how you're getting the subscription details, so long as you get the details) to receive a Purchases.subscriptions resource.
From there, you could compare the startTimeMillis and expiryTimeMillis. If it's 7-days, it's a trial, else, an actual subscription. (similar idea as to what was mentioned in this answer.
With all that said, I would like to point out that I'm not entirely sure if this is good practice in determining free trial subscriptions.
I am having confusion choosing a suitable product type of a commodity in my utility android application.
My application is of a kind where the user is benefitted through the number of 'slots' that they own. Think of it like the length of a ListView. My free application will feature a maximum length of that ListView upto 5. But if a user needs more List Items, they can buy subsequent List Item slots. The user should be able to buy as many as they like at 0.50$ each.
The problem is that, my in-app product neither comes under the definition of a 'managed product' alone, nor does it come under the definition of a 'consumable product' alone. I need the 'slot' product able to be purchased as many times as the user likes. But at same time, i also want Google Play to keep track of the ownership of the amount of slots per user.
As the documentation (and several tutorials out there) suggest:
If i want Google Play to store the purchase information for each item on a per-user basis, i will have to declare my product as a managed (non-consumable) product.
If i want my product able to be purchased many times, i would have to implement consumption for items that can be purchased multiple times.
The predicament is that i want both the features.
One bad solution is that I feature several slot products as unique products (alpha slot, beta slot, ... and so on), and then treat them as the same thing. However, i'd like to know of a solution that's more correct and sensible.
Another solution could be to make use of a separate cloud service to keep track of the number of slots bought by users, but i don't want that. That would be overkill. Also, i'd like to make use of the 'local caching' feature of Google Play Store.
I asked Google Play Developer Support if there were any way that Google Play Store could keep count of consumable in-app products. Turns out, at the moment, it is not possible.
Here is the actual reply (Nov 3, 2016):
I wanted to know whether Play Store also keeps record of the count of a
CONSUMABLE in-app item that a user has bought even after the user
uninstalls and then reinstalls the app?
In short, after user reinstalls, is there any way of knowing as to how many
of a single in-app item has the user ever bought?
Hi,
Thanks for contacting Google Play Developer Support.
I checked into it and the Play Developer Console doesn’t currently
support tracking every individual users every IAP purchase for
developers to see. Luckily, we place a high value on developer
feedback, and I’ll be sure to pass along your specific feedback to our
product team. We’re continually adding new features and functionality,
so please stay tuned.
You can always check the Android Developers Blog for any new features
and updates: http://android-developers.blogspot.com/
Is there a way to buy a multiple of an item (SKU)?
For example: I have an SKU named LifePoint, and i want to buy 100 LifePoint.
(in another word N * my_item).
I found this workaround, but am looking for something better.
PS: I'am using In-app Billing Version 3 and my item is Unmanaged (to buy my product many times).
Thanks in advance!
I'm pretty sure that it's been made deliberately impossible to do that to prevent apps from stealing large amounts of money from the user. Each purchase requires confirmation using the Google in-app billing popup which stops that kind of thing happening.
The way I've done something similar is to set up different SKU's (as in the answer you linked to) but to use the SKU 'title' field to decide how many items to award the player. If you do it that way you can easily change those values in the Google Play console without having to update your app.
Before I ask the question, I have gone through the Android In-App billing documentation, and some similar questions on StackOverflow like this. Also I am guessing this question may be closed or down-voted, as its not a direct programming question and is subjective in nature. But still, here it goes.
The implementation works fine. My question is what would be a good way to store the in-app purchases locally. In my case, there is only one one-time unlockable item which unlocks full functionality of the app, and does not expire or get consumed.
Shared Preferences seems a little too easy for piracy. Even though querying the Google Play works fine and seems relatively secure, is it good enough to rely on? Since the documentation recommends to make the call asynchronously, it doesn't sound like a good idea that the user who has paid for the unlocked features, might have to wait to get access to the unlocked features. Also, when the GP cache is cleared, it takes longer than usual (which might be a rare scenario).
But seeing that there are so many apps and developers, I am wondering how other developers handle it?
Actually i think that your question will be closed for a few different reasons, but i will give you some insights.
You have a few options, depending on the nature of the products, and in some other things.
If the product is valid for life time, or depending on time (for instante, updates for a year) you can rely in google play, and that could be good.
If the product is something that the user will expend in different ways inside the application, such as in-game currency, extra lives, etc, you should save it locally, in a sqlite database, or in a remote database if you want to use a server to centralize some services.
If you take a look at the section "Consume a Purchase" of the guide purchase iab products you can read:
Once an item is purchased, it is considered to be "owned" and cannot
be purchased again from Google Play while in that state. You must send
a consumption request for the item before Google Play makes it
available for purchase again. All managed in-app products are
consumable. How you use the consumption mechanism in your app is up to
you. Typically, you would implement consumption for products with
temporary benefits that users may want to purchase multiple times (for
example, in-game currency or replenishable game tokens). You would
typically not want to implement consumption for products that are
purchased once and provide a permanent effect (for example, a premium
upgrade).
For consumable products:
It's your responsibility to control and track how the in-app product
is provisioned to the user. For example, if the user purchased in-game
currency, you should update the player's inventory with the amount of
currency purchased.
Security Recommendation: You must send a consumption request before
provisioning the benefit of the consumable in-app purchase to the
user. Make sure that you have received a successful consumption
response from Google Play before you provision the item.
Anyways, at any range i would recommend to keep track of all the products that an user buys and all the privileges that he has by yourself, since it is faster, and it could be useful for some other things like custom stats, reporting, etc.
You should use a backend web server if you're concerned about piracy. Google has a web api for checking if a purchase is legit. Have the device contact your web server. Have the web server validate with Google. If valid then let the device download your assets from the server.
If the in app item is not an asset, but a consumable item like in a game maybe have your server return an encrypted string that your app knows how to decipher.
If you rely completely on locally stored purchase info it will be very easy to spoof.
I've gotten IAB v3 working and I was able to make a purchase for a managed item. However, to continue developing and testing I wanted to refund the purchase so I could try making the same purchase again. I logged into my Google Checkout Merchant account and successfully refunded the purchase. However, the app still thinks that the user has the item purchased. It has already been several weeks since I made the refund so its not a delay issue.
Basically, in my QueryInventoryFinishedListener implementation, inventory.hasPurchase(SKU_REMOVE_ADS) always returns true, even after the refund (SKU_REMOVE_ADS is the SKU for item I'm selling). I was expecting it to return false after the refund had been processed.
If you look at the 'Handling Refunds' section of the IAB reference, it says that your app needs to be listening to the IN_APP_NOTIFY messages. However the documentation for IN_APP_NOTIFY is specific to v2 of in-app billing. It doesn't seem to be something that's available in v3 since its not mentioned anywhere in the v3 reference nor can I find any reference for it in the sample TrivialDrive app that they are using to demonstrate IAB v3.
So does v3 of IAB support refunds/cancelling purchases? Has any one tried it and got it working?
There really is no difference between a consumable item and a non-consumable item so far as Google Play is concerned; this distinction is entirely based on what you implement within your app. So even though the SKU you are testing is intended to be non-consumable (e.g., a permanent premium upgrade), for testing purposes, you can treat it as a consumable and consume it, so that it can be purchased again.
A convenient approach is to set up a temporary testing menu within your app (e.g., by adding a menu item during testing onto your app's main options menu), and then to have that item's handler invoke the consumeAsync() method of your IabHelper instance for the SKU that you want to test buying again. This will consume the item and thus make it immediately available for repurchase from your device.
You will, of course, still want to refund the purchase from Google Checkout, so that you won't be spending your own money just to test your app.
I would add that consumeAsync() also seems to work just fine for resetting the test SKU android.test.purchased, if you are testing using such static values.
Regarding the updating of purchase state to reflect a refund, I have personally experienced (and there are many similar reports posted by other developers) that manually initiating a refund via Checkout (e.g., for a test purchase from the TrivialDrive app) takes days to result in a change to the purchase state of the product (to INAPP_PURCHASE_STATE_REFUNDED).
(Knowing that misery loves company, some of those additional reports can be found on this discussion thread:
https://plus.google.com/+AndroidDevelopers/posts/R8DKwZDsz5m)
At least part of this is due to Google Play's caching of purchase data on the device.
In my experience, re-booting a device can sometimes cause Google Play to refresh its cache from the GP servers. So it may be that changes due to cancellation or refunding of an order via Checkout could also be detected after a reboot.
It might seem that such a long turnaround period would do you no good, since you can't know when users will reboot. But then again, you know that every device will, eventually, get rebooted, and so if your concern is that a user who receives a refund should eventually be blocked from using the refunded IAB product, a few days of delay may not matter much, so long as it eventually happens.
Of course, remember that this notion that cache will refresh on a reboot is undocumented and anecdotal (like quite a number of IAB3 and TrivialDrive behaviors, thus far). Folklore, they call it.
Another thing that triggers an update is when the user attempts to purchase the product. As soon as the purchase is launched, the system has to be sure that the product is not already owned, and so it updates the Google Play cache. In my personal experience, this has always occurred. But again, this is not a very practical way to check for a refund, because that would involve showing the purchase dialog unbidden, and also an error message that tells the user "you already own this, " (if they do own it).
Where this does come in handy is when the user pays for an IAB item on one of her devices, and then attempts to access that item on a different device that is owned by the same account as was used to buy it. The purchase information in that case has very often not yet been cached. But you can just put a little note in your purchase dialog that if the item has already been purchased, attempting a re-purchase should make it available on the present device at no additional charge. Sometimes it takes two (user-initiated) purchase attempts to finally get the IabHelper.BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED response. Yes, a bit klugy, but I think in human terms it will work with appropriate highlighting of the message and apologetic wording of the confirmation dialog telling them that they own the item, etc. :-) ).
As a practical matter, you can see how Google might not want every instance of every IAB app in the world to access its servers every time the app's purchase data is being accessed, especially given that they are advising developers to do a check for what has been purchased each time the app is started. It's also a performance issue for your app - that's what caching is all about. So you need to be aware of the triggers for updating the cache, and I haven't found a single place where this is officially documented (except, we presume, in the code). So get ready to put your hands out in front of you and start feeling around in the dark.
For some additional information regarding Google Play buffering, see this page:
Under What Conditions are In-App Billing Version 3 Server Changes Made Available on Client Devices?
I would note that in your post's code snippet you are calling inventory.hasPurchase(SKU_REMOVE_ADS), but that will only tell you if the purchase is in the list of purchases returned in the inventory object; it will not tell you the state of the purchase for that SKU. I know that this is the approach used by the TrivialDrive app, but that app is not dealing with refunds and cancellations. To detect refunds and canceled orders, you'll need something like this:
Purchase removeAdsPurchase = inventory.getPurchase(SKU_REMOVE_ADS);
if(removeAdsPurchase != null) {
int purchaseStateForRemoveAds = removeAdsPurchase.getPurchaseState();
if(purchaseStateForRemoveAds == 1) {
//Do cancelled purchase stuff here
}
else if(purchaseStateForRemoveAds == 2) {
//Do refunded purchase stuff here
}
}
The good news about refunds and canceled orders is that both are, AFAIK, entirely at the option of the developer. So, if you find that users who get these are able to continue using your app for a long interval thereafter, and if you find that lots of users are taking advantage of this, then you can decide if you want to continue providing the refunds in all cases. My best guess is that it will not be a problem; even if some user who gets a refund gets to use your app for a while after that, that doesn't seen like a very big deal.
It is for testing that you need the ability to re-try a purchase very rapidly, and using consumeAsync() definitely works for that purpose.
I will suggest you to use static product ids while your app is in development phase.
Now make sure you are testing the app with same Gmail Id for which you have refund? To test the refund scenario I think you can use android.test.refundedas product id.
If this is not working then you can first check total purchased item(s) and Available item(s) in google play at first launch of your app and if you are getting same product id in both the calls(which should not be the case if this is the case please report this bug to google) then make api call to make same item as consumed.
Since posting this question, its been brought to my attention that I need to call getPurchase(...).getPurchaseState() and check for its value. Possible values are 0 (purchased), 1 (canceled), or 2 (refunded).
However, in my case its still retuning 0 (purhcased) even though the item is refunded. I'm posting this information here in case it helps someone else.