I have 4 subscription types in the app I'm developing. Two monthly (one with discount) and two yearly (one with discount). I am in the testing step. When I ask the purchases history I am not getting all the lasts subscriptions of each type as the documentation says. And also the purchases I get are not the lasts of each SKU type. Did anyone have this issue?
It's giving me problems with grace period as I don't get the last purchase I did so I don't get the updated expiry time.
I checked Google Play Console order management and I see the purchases, and also in my Google Play app, so I don't know what's the problem?
It's difficult to understand your situation without you providing a code snippet. But here is a general implementation of queryPurchaseHistoryAsync. If you are still having problems, please provide more context such as your actual code snippet and where you are making the call. For the following snippet -- just for testing -- I am making the call right before I call queryPurchases.
private fun queryPurchaseHistoryAsync(){
playStoreBillingClient.queryPurchaseHistoryAsync(BillingClient.SkuType.SUBS){
responseCode, purchasesList ->
if(purchasesList.isNullOrEmpty()){
Log.d(LOG_TAG,"history for SUBS is empty")
}else{
Log.d(LOG_TAG,"history subs has ${purchasesList.size} items : ${purchasesList.toString()}")
}
}
playStoreBillingClient.queryPurchaseHistoryAsync(BillingClient.SkuType.INAPP){
responseCode, purchasesList ->
if(purchasesList.isNullOrEmpty()){
Log.d(LOG_TAG,"history for INAPP is empty")
}else{
Log.d(LOG_TAG,"history INAPP has ${purchasesList.size} items : ${purchasesList.toString()}")
}
}
}
Also in my cases, I have no problem getting the purchase histories.
Related
I've been managing Google Play subscriptions from my Android app for a long time, using Google Play Billing Library version 4.0 since last year (it's not feasible to upgrade to 5.0 at this moment). Now I want to offer free trial periods in subscriptions, so I have created one offer in the Google Play console for each of my subscriptions, with the following configuration:
Offer's eligibility criteria: Developer determined (I don't want Google to decide which users may enjoy the offers, I want to be the one to decide it from my code, applying my own criteria)
A phase of "Free trial" type, 1 month duration.
When I need, for example, to get the details of a "my_monthly_plan" subscription from my app, my code is as follows:
val params = SkuDetailsParams.newBuilder().apply {
setSkusList(listOf("my_monthly_plan"))
setType(SkuType.SUBS)
}
myBillingClient.querySkuDetailsAsync(params.build()) { billingResult, skuDetailsList ->
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
if (!skuDetailsList.isNullOrEmpty()) {
// A not empty products details list has been successfully retrieved.
...
} else {
// Products details list is null or empty.
...
}
} else {
// The response code is not OK.
...
}
}
}
If everything is ok, I receive a SkuDetails object containing something like this:
SkuDetails: {"productId":"my_monthly_plan", ... ,"price":"49,00 €", ... ,"freeTrialPeriod":"P1M"}
First problem: I always receive here an empty freeTrialPeriod object. All subscriptions and offers are correctly created on Google Play. Maybe I just have to wait a few more hours or days for them to be visible from my app?
When users choose which subscription (defined by skuDetails) they want to buy from my app, the following code is executed:
val flowParams = BillingFlowParams.newBuilder()
.setSkuDetails(skuDetails)
.build()
billingClient.launchBillingFlow(activity, flowParams)
Second problem: at this point I have no idea how to tell Google if the free trial should be applied to the purchase or not.
Let me explain with a couple of example scenarios:
Scenario 1
I have a customer A who has never bought subscriptions in my app, and I want him to enjoy the free trial when he buys for the first time.
The querySkuDetailsAsync call returns a plan with these SkuDetails: {"productId":"my_monthly_plan", ... ,"price":"€49.00", ... ,"SkuDetailsParams":"P1M"} (I am optimistic and I hope that my first problem will be solved and I get to receive a non-empty value in SkuDetailsParams)
When customer A purchases the subscription, I use the SkuDetails obtained in the previous step to parameterize the launchBillingFlow call and launch the purchase process.
I assume that Google is going to apply the free trial to my customer A for one month, and if the customer auto-renews the subscription after this free month will automatically be charged the price without offer, €49 each month. But, how can I be sure that the customer is not going to be charged €49 from this first purchase? Do I need to specify it somehow when calling to launchBillingFlow? Or maybe there is another API call for this?
Scenario 2
I have another customer B who has never bought subscriptions in my app either, but for whatever reason I don't want him to enjoy the free trial when he buys for the first time (that's why I chose the "Developer determined" Offer's eligibility criteria).
The querySkuDetailsAsync call returns the same SkuDetails as before.
When customer B purchases the subscription, should I use the same SkuDetails to parameterize the launchBillingFlow call to launch the purchase process? How do I tell Google that the free trial does not apply to this customer B, that he has to be charged €49 from this first purchase?
Any idea to solve my two problems will be very welcome. Thanks in advance.
I have an app with a subscription in Google Play.
When the user starts the app, I need to know if the user has an active subscription. This would seem an obvious thing to do, but from searching and trying to implement it, it seems impossible?
I am using Google's newer billing 2/3, following Google's tutorials,
class BillingManager implements PurchasesUpdatedListener
...
public void checkAsync() {
Log.e(TAG, "checkAsync");
billingClient.queryPurchaseHistoryAsync(BillingClient.SkuType.SUBS, new PurchaseHistoryResponseListener() {
#Override
public void onPurchaseHistoryResponse(BillingResult billingResult, List<PurchaseHistoryRecord> list) {
Log.e(TAG, "checkCached result: " + list);
if (list == null) {
return;
}
for (PurchaseHistoryRecord ps : list) {
//System.out.println("PAYMENT: " + ps.getSku() + " : " + ps.getPurchaseTime());
}
}
});
}
public void checkCached() {
Log.e(TAG, "checkCached");
List<Purchase> result = billingClient.queryPurchases(BillingClient.SkuType.SUBS).getPurchasesList();
Log.e(TAG, "checkCached result: " + result);
if (result == null) {
return;
}
for (Purchase purchase : result) {
handlePurchase(purchase);
}
}
This is how I think you're supposed to get a user's purchases. But it does not work at all, both calls return null always. It only returns the correct purchases when you reinstall the app or clear the data.
So how exactly is an app supposed to do this?
Purchasing works for the app once I enter internal testing, and download it through the Google Play link. (before that subscriptions do not work at all).
*** updated
So to further clarify:
I am using a valid test user, and subscriptions are working correctly. My question is on the what the API queryPurchases() or queryPurchaseHistoryAsync() are suppose to do.
What I am seeing, is that these only return purchases that have not be processed by the app. They seem to store that the purchase was processed in the apps data.
After the purchase these return null, after the app restarts these return null.
If I clear the app datam or reinstall the app then they return the purchase (once), then again null after restart.
From what I see, these are only useful to detect when a user reinstalls your app, or installs on a different phone. They cannot be used to determine the status of a subscription.
So my question is,
1 - is this something that just does not work in internal testing and will magically work differently when the app is release?
2 - is there a different API that your suppose to use to check the status of a subscription?
3 - are you suppose to manage subscriptions yourself in your app by storing a user preference/cookie when you acknowledge the subscription the first time so you know when the subscription expires?
You need "licenced testers". They would allow you to "sideload" your app on devices, even for debug builds. My interpretation of sideload in this case would cover installing from Android Studio build tools as well as adb install .... and other methods that don't involve the play store.
https://developer.android.com/google/play/billing/test
Ordinarily, the Google Play Billing API is blocked for apps that aren't signed and uploaded to Google Play. License testers can bypass this check, meaning you can sideload apps for testing, even for apps using debug builds with debug signatures without the need to upload to the new version of your app. Note that the package name must match that of the app that is configured for Google Play, and the Google account must be a license tester for the Google Play Console account.
I also don't see how you're using startConnection. Until that's completed successfully I wouldn't be sure you have the latest data. I wouldn't be surprised if that makes you get stale values. I would check that carefully to make sure there's no silent errors happening, by both looking at onBillingSetupFinished and onBillingServiceDisconnected. And for the time being avoid trusting queryPurchases():
https://medium.com/#NandagopalR/integrating-google-play-billing-into-an-android-application-b6eb6af176a7
The queryPurchases() method uses a cache of the Google Play Store app without initiating a network request. If you need to check the most recent purchase made by the user for each product ID, you can use queryPurchaseHistoryAsync(), passing the purchase type and a PurchaseHistoryResponseListener to handle the query result.
By the way what's the value of isReady() right before queryPurchaseHistoryAsync, and what's the value of BillingResult::getDebugMessage and BillingResult::getResponseCode?
Also, use isFeatureSupported, though it seems it's not like your problem is coming from here. But I'd advise not testing with subscriptions until you get all the moving parts working: https://developer.android.com/reference/com/android/billingclient/api/BillingClient#isFeatureSupported(java.lang.String)
Okay, figured it out, was my mistake.
I was calling queryPurchases() in my main activity onCreate(), but the BillingClient was not ready yet.
I moved it to onBillingSetupFinished() and it now returns the correct purchases.
Everything is now working as expected. You get the active subscriptions when you call queryPurchases() after an app restart.
Until june 20th 2016 i was able to cancel test purchases done in my app.
Doing multiple in-app purchases (not consumable) from the same test account made it easy to develop and test the code without too much hazzle.
After 20th june 2016, the purchases did not show in my merchant account and
i was unable to do more than 1 purchase from my test account. All i got was the: "you already own this item" message.
I logged a request to the google developer support group and the answer was:
Beginning June 20, 2016, we changed test purchases for one-time in-app purchases (IAPs).
Previously, test purchases for one-time IAPs generated order IDs. Starting June 20, 2016, one-time IAPs do not generate official order IDs (if at all) and will not appear in the Merchant Center. This behavior already applies to subscription IAPs.
You can learn more about testing in-app billing in the Android Developers Help Center: https://developer.android.com/google/play/billing/billing_testing.html#testing-purchases
allright.. so i go to the mentioned link and theres a section there:
Canceling completed test purchases
which states:
Google Play accumulates completed test purchases for each user but does not pass them on to financial processing.
In some cases, you might want to manually cancel a test purchase to continue testing. To do so, open the app page in the Play Store. If the test purchase that you want to cancel is a subscription, you can also use the cancel() method of the Purchases.subscriptions API.
Important: The refund() and revoke() methods of the Purchases.subscriptions API don't support test purchases.
So I go to the app page in play store...and do what exactly? the webpage does not state what i am supposed to do there. anyone know?
it does say: you can also use the cancel() method of the Purchases.subscriptions API.
which indicates that using the cancel() method is not the only method.
How to solve this without adding additional code in my app?
I went into the main Google Play Console page and clicked on Order Management. Under that I was able to select all test purchases and Refund them. I'm the primary developer of the app so I have access. If you are a tester you'd probably have to contact the support team and request that they refund your order.
All managed in-app products are consumable.
as stated in the docs.
That means that you can consume an owned item instead of cancelling the purchase and buy it all over again.
I suggest querying the inventory at the app launch time:
mIabHelper.queryInventoryAsync(this);
You can then consume the owned item in the callback:
#Override
public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
Purchase purchase = inventory.getPurchase(MY_SKU);
boolean isBought = (purchase != null && verifyDeveloperPayload(purchase));
if (isBought) {
mIabHelper.consumeAsync(purchase, new OnConsumeFinishedListener() {
#Override
public void onConsumeFinished(Purchase purchase, IabResult result) {
//Clear the purchase info from persistent storage
}
});
}
}
This is OK for testing the IAB flow but make sure to remove this code from the release version.
On Play Console, go to Developer Account -> Account Details, set the license testers (you are one by default)
Purchase items
Go to Order Management, choose those test orders, select: refund, but DON'T FORGET to check REVOKE when you are refunding. (I forgot to revoke, and now can't find a way to take them back)
Anyway, another test account will do.
I found a solution which isn't very convenient, but works. It seems like you can consume uncomsumable products and that way you can buy them again. I'm working with phonegap so I only have example code for the cordova-plugin-purchase plugin:
store.when("your.product.id").updated(product => {
if(product.owned) {
var transaction = product.transaction;
product.transaction = null;
store.inappbilling.consumePurchase(
function() { // success
alert("consume success");
},
function(err, code) { // error
alert("consume error " + err)
},
product.id,
transaction.id
);
}
});
The updated callback gets called when you call store.refresh() or buy the product. So depending on your use case you'd want to implement an additional method of checking when to consume the product.
I have no experience with the native Android in-app payments, but obviously you will be able to consume the products there as well.
Edit: Sorry, I just read that you didn't want to include additional code in your project. I don't think that's possible at the moment, but would like to keep my answer here because it might help other people trying to test in-app payments.
What worked for me was a combination of both:
Go to order management and refund
clear cache/data in Play Store app (as well as your app in you placed some shared prefs).
Also, in case you get an item already owned status, you can consume the purchase using the purchase token and calling billingClient.consumeAsync().
Didn't find a solution for this.
My workaround is simply remove the current test user from the test users list, make a real purchase, then cancel it using the merchant console.
The queryPurchaseHistoryAsync method still finds test orders I've made over the last year, despite having long ago consumed, refunded, and revoked them. One precaution I've found helpful is clearing the data in the Google Play Store app (settings/apps/google play store/storage/clear data). queryPurchaseHistoryAsync pulls obsolete purchase data from here, although only the non-networking (and completely unreliable I've found) queryPurchases is supposed to do this. You may have to add additional code to your app after all, but it doesn't have to be much.
With the dropping of support for Trivial Drive 2 (the link in the docs takes you to a '404 page does not exist' error, the github files are archived, and updating to billing:2.1.0 will give you a vending import compile error in the IabHelper class), answers to this popular question involving IabHelper might be considered obsolete. Billing is a lot simpler now if you follow the basic code pieces in the docs starting here https://developer.android.com/google/play/billing/billing_overview with no messy helper classes. One persistent issue is a 'Both methods have same erasure, yet neither overides the other' method clash error you may run into with this implementation, see my solution here 'Both methods have same erasure, yet neither overides the other' method clash error in SkuDetailsResponseListener().
Once you have the newest billing code implemented, you can create a hidden trigger in your production app to call queryPurchaseHistoryAsync to get a purchaseHistoryRecordList. Then call consumeAsync for each item in this list. Here is the barebones code to consume all test orders, allowing multiple tests of your nonconsumables:
billingClient.queryPurchaseHistoryAsync(BillingClient.SkuType.INAPP,
new PurchaseHistoryResponseListener() {
#Override
public void onPurchaseHistoryResponse(BillingResult billingResult,
List<PurchaseHistoryRecord> purchaseHistoryRecordList){
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK
&& purchaseHistoryRecordList != null) {
for (PurchaseHistoryRecord purchaserecord : purchaseHistoryRecordList) {
if(purchaserecord!=null){
ConsumeParams consumeParams =
ConsumeParams.newBuilder()
.setPurchaseToken(purchaserecord.getPurchaseToken())
.setDeveloperPayload(purchaserecord.getDeveloperPayload())
.build();
billingClient.consumeAsync(consumeParams, consumelistener);
}}}}});
For people using a way based on the new TrivialDriveKotlin, consumable products are consumed during the installation of the app in the method
handleConsumablePurchasesAsync
If your purchase is not consumable, you can make it consumable by adding the corresponding sku into CONSUMABLE_SKUS in the GameSku object. Exemple:
val CONSUMABLE_SKUS = listOf(GAS, PREMIUM_CAR)
Then uninstall your app from your device and install it again, and your non consumable purchase is available again. Fast and simply.
Of course, don't forget to remove your
Am trying out in-app billing in my App for the first time. Am using the labHelper code (https://gist.github.com/yigit/4543005) from the TriviaDrive example in the samples folder in the Play Billing Services extras. When I tried with the test code that Google gives i.e. product code as 'android.test.purchased' (give here under 'Testing with static responses'), it worked fine. But now, I switched to using the test Google account (i.e. license testing, given under 'Setting up test accounts' here). I purchased the item successfully. But when I try to use a purchased item (it's a monthly subscription product), I get an error dialog saying 'You already own this item'. At the same time, in the logs, I see the message
05-02 17:10:36.599: D/Finsky(6396): 1 PurchaseFragment.handleError: Error: PurchaseError{type=3 subtype=3}
05-02 17:10:36.599: D/Finsky(6396): 1 PurchaseFragment.fail: Purchase failed: PurchaseError{type=3 subtype=3}
(response code 3 meaning 'billing unavailable')
But, if I dismiss the error dialog, I get response 7 (i.e. 'you already own this item').
Not sure why I keep getting 3 for an item that's already been purchased? Please help.
What I tried
I tried calling startSetup() and the listener OnIabSetupFinishedListener() in the Activity's onCreate(). Am getting response 0.
Then I tried labHelper class' 'queryInventoryAsync(mReceivedInventoryListener)' method and it also returns 3.
I tried consumption related methods from labHelper class
launchSubscriptionPurchaseFlow(this,
InAppBillingExportProductId,
10001, mPurchaseFinishedListener, "”);
mPurchaseFinishedListener returns the response 0 (i.e. success), but it immediately returns the error -1010 (IABHELPER_INVALID_CONSUMPTION)
Please help. Been struggling with this one issue for more than 3 days now!
IabHelper has a method called enableDebugLogging(...) that you can use (call it with true) to turn on pretty detailed logging for all IabHelper actions. If you can post a copy of the full log, it might be easier to figure out exactly what's going on.
But let me post a couple thoughts anyways based on what you wrote:
Just like normal managed products, subscription items can not be purchased again if they are already owned.
The way to check ownership is via queryInventoryAsync(...) (if inventory.getPurchase(sku) is not null, you own the item). Unfortunately this is not always 100% accurate and I haven't found a fix yet.
To "use" a managed product or subscription, simply have your application provide whatever service you sold if the above check tells you that the item is owned.
The big difference between managed products and subscriptions is the way that the user loses them again (i.e. can purchase the same sku for a second or third time):
Subscriptions expire automatically at the end of a pre-determined period if the user doesn't renew them,
while managed products need to actively be "consumed" by your app to make them available again.
So, if you try to purchase a managed product again before "consuming" it, you will get the "you already own this item"-error that you mentioned. Same goes for trying to purchase a subscription again that you already purchased and that hasn't expired yet.
The "IABHELPER_INVALID_CONSUMPTION" error is likely caused by the fact that subscriptions cannot be consumed, only managed products can. Subscriptions only expire (or you can refund them from the developer console).
I'm not sure where the "PurchaseError{type=3 subtype=3}"-error is coming from. That one might be easier to track down from the actual IabHelper debug output. If you can update your question with a full log, send me a comment and I can take a look at it and probably help you make sense of it if needed.
Let me know if you have any further questions. I hope this helps.
I set up a beta account to test IAP for google app that I am working on, the issue I have is, once I have purchased One-time products(non-recurring charge) the test IAP, I cannot 'remove it' as such, so now, even when I delete the app and re-install, it remembers the purchase, that's great in the real world for a user, but not great when trying to fix the bugs!
Is there any way (short of making a ton of gmail accounts to test with) to remove the purchase from the account?
This is an old question but if someone is still looking for a solution then go to:
Google Play console and open the Order Management tab
There you can refund / cancel test purchases. Then clear the purchase state using this command:
adb shell pm clear com.android.vending
The only way I know is to force a consume in your app. You can then remove that code.
I am using cc.fovea.cordova.purchase plugin for cordova to manage my IAP purchases. To get my test Non-Consumables to be deleted I changed my registration from Non-consumable to Consumable.
store.register({
id: this.predatorID,
alias: 'Predator Pack',
type: store.CONSUMABLE //store.NON_CONSUMABLE
});
Also, apparently there are reserved keywords you could use instead (if you're into that). - https://developer.android.com/google/play/billing/billing_testing.html
I encountered the same situation and started to research. Unfortunately, the directions made here did not produce a solution.
I want to share the solution that worked for me.
If you call the method below in the right place, the solution will be produced. Source : Link
/**
* Recall that Google Play Billing only supports two SKU types:
* [in-app products][BillingClient.SkuType.INAPP] and
* [subscriptions][BillingClient.SkuType.SUBS]. In-app products are actual items that a
* user can buy, such as a house or food; subscriptions refer to services that a user must
* pay for regularly, such as auto-insurance. Subscriptions are not consumable.
*
* Play Billing provides methods for consuming in-app products because they understand that
* apps may sell items that users will keep forever (i.e. never consume) such as a house,
* and consumable items that users will need to keep buying such as food. Nevertheless, Google
* Play leaves the distinction for which in-app products are consumable entirely up to you.
*
* If an app wants its users to be able to keep buying an item, it must call
* [BillingClient.consumeAsync] each time they buy it. This is because Google Play won't let
* users buy items that they've previously bought but haven't consumed. In Trivial Drive, for
* example, consumeAsync is called each time the user buys gas; otherwise they would never be
* able to buy gas or drive again once the tank becomes empty.
*/
private fun clearIapHistory() {
billingClient!!.queryPurchases(BillingClient.SkuType.INAPP).purchasesList
.forEach {
val params =
ConsumeParams.newBuilder().setPurchaseToken(it.purchaseToken).build()
billingClient!!.consumeAsync(params) { responseCode, purchaseToken ->
when (responseCode.responseCode) {
BillingClient.BillingResponseCode.OK -> {
}
else -> {
Log.w(LOG_TAG, responseCode.debugMessage)
}
}
}
}
}
if (inventory.getPurchase(ITEM_SKU) != null ) {
try {
mIabHelper.consumeAsync(premiumPurchase, new IabHelper.OnConsumeFinishedListener() {
#Override
public void onConsumeFinished(Purchase purchase, IabResult result) {
Toast.makeText(MainActivity.this, "Consumed the test purchase successfully", Toast.LENGTH_SHORT).show();
}
});
} catch (IabHelper.IabAsyncInProgressException e) {
e.printStackTrace();
}
}
However refund() and revoke() methods don't support test purchases and you are left with only consumeAsync() option.
Just:
Purchase unlockedPurchase = inventory.getPurchase(SKU_UNLOCKED);
// Log unlockedPurchase.getOrderId();
Go to your Google Play panel, Order management, look for that order id and refund it (it should say Test order if it is your own order).
I guess the only method working is to...
Consume It!
For further info, get to the consuming document and search for "consume": https://developer.android.com/google/play/billing/integrate
Here are the important steps for you:
Dependence setup.
Billing client connection.
Query the Purchase.
Consume(Purchase).
Good Luck~
Test non-consumable products
To perform multiple test purchases for the same non-consumable product, you can refund and revoke purchases using Google Play Console.
I had a similar issue. Fortunately, the app I'm working with is WebView-based, so I can easily inject a link or button to trigger some Javascript to call back into the application to consume the test orders. Since test orders have an empty string for the orderId, it is easy to identify them to consume them. Once consumed, the item can be "purchased" again. Removing the button requires commenting out one line of code BUT if the button accidentally makes it into the final published app, it won't cause any problems since the code only consumes test orders - that is, real orders are not affected. That button will just be embarrassing instead of a disaster.
I'm working on a device without a credit card associated with it. I set up some promo codes and use the "Redeem Code" option for my test orders. Promo codes result in no risk of money exchanging hands and I'm able to completely verify IAB functionality in my app with real products without having to resort to the IAB test codes.
Nothing shows up for me in Google Wallet as per the post by Martin Kool.
You can refund the test purchase of non-consumable product and it will work just fine.
Using the Play Console website
Open Play Console.
On All Apps page left side, select Order Management
Use the "Search orders" box to search by order ID or the user's full email address.
Open order, click on Refund button. Select Entitlement checkbox and refund.
More details here about refunds: https://support.google.com/googleplay/android-developer/answer/2741495
Google Play Purchases are stored in Google Wallet.
https://wallet.google.com
When signed, go to "Transactions" on the left. Test purchases can be cancelled from there.