In App billing v5 Wrong pricing in Android - android

I'm using InAppbilling v5 in Android to retrieve the price of the Items, everything is working correctly, however when "Google" review my app they rejected because I'm not following the Subscription guidelines. In my test the price is retrived correctly but for in the Screenshots of Google there is a "Free", formatted price string instead $9.00 so I'm not sure how to fix this issue because apparently only happens when Google review my app. I'm using the following code to retrieve the products
billingClient.queryProductDetails(
subscriptions.toQueryProduct(ProductType.SUBS)
).productDetailsList
Then I'm retrieving the offer details like this:
val price = productDetails.subscriptionOfferDetails
?.firstOrNull()
?.pricingPhases?.pricingPhaseList
?.firstOrNull()
?.formattedPrice ?: ""
In my devices and emulator works correctly displaying the correct price, however for google it display "free" as formattedPrice How I can solve this?
In my Subscriptions in the Play Store console I have only one price/base offer with a 7 days free trial.

I found the issue, the problem was that in the new version v5 if the user never had any subscription there will be 2 phases, the "free" trial which price is free and the subscription price after trial, I tested with my user and since I already had any subscription the "free" was not available, but for new users that never had any subscription there was a free opion. for now in my code I'm filtering the free with this code
// there can be several pricingPhases and trial can be "free"
val formattedPrice = offerDetails?.pricingPhases
?.pricingPhaseList
?.firstOrNull { it.priceAmountMicros > 0 }
?.formattedPrice
using it.priceAmountMicros > 0 will filter any item that is not "free" or has a price

Related

Manage subscription free trials with Google Play Billing Library from Android Studio

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.

Android In-App Purchase Fails Unless User Clears Play Services cache

Our app has an in-app purchase to upgrade to our "Pro" product, in Google-speak this is a one-time non-consumable product. We have launched this product and are actually generating revenue...so aside from our own testing we know that across the globe users are successfully seeing on our in-app sales page 1) the price of the product, and 2) an enabled "buy now" button (please note these two items both are derived from this BillingManager query:
launch {
val sku = billingManager.querySKU(BillingManager.SKUProPack1)
if (sku != null) {
iapPriceText.text = getString(R.string.settings_pro_upgrade_price, sku.price)
skuDetails = sku
upgradeButton.isEnabled = true
} else {
iapPriceText.text = ""
upgradeButton.isEnabled = false
}
}
I'll point out here: skuDetails collected in the query above is also the source of data needed in the actual Play Store purchase flow...so merely enabling the button would only delay the disappointment. The first indication a user sees that there is a problem is on the sales page: no price and a greyed-out button that is disabled.
We have three confirmed cases where the user has contacted us saying "I'd like to buy but the buy-it button doesn't do anything". We have found an ugly-ish workaround which is to have the user clear the cache of their Google Play Services app (and then reset the device). After they do that, they can get back to the sales page and all is well: the price is displayed and the button is enabled.
I'm thinking the step of clearing the cache is a clue to perhaps something that could/should be done within our app to better serve the customer (and, of course, we hope generates more revenue). It's early days for us in this Pro version but the three confirmed cases would be about 2% of the amount who have gotten through the purchase process...which doesn't sound like a lot, but who knows how many have fruitlessly pounded on the buy-it button and then just walked away.
On this, I'm going to file a ticket with Google and will keep all posted here if that yields anything. AND HERE IT IS:
As promised, here is Google's non-response. The link they provided was nice but it did not include the troubleshooting step that we found that works for us (clearing the Play Services cache). Here is Google's response: Hi,
Thanks for your patience.
I'm sorry to hear that some of your users are having a issue making a purchase within your app. Please note after looking into your issue, it looks like the issue is being caused by user end issues. As not all of your users are having the same issue, it would not be a issue within the coding. We are aware that cache and connectivity can cause issues within apps; and we have made a user help article to help guide users on how to fix these issues: https://support.google.com/googleplay/answer/1050566?hl=en
If you begin seeing more users with this issue, you can use log reporting to help determine the issue, you can use the Android logging system which provides a mechanism for collecting and viewing system debug output.
To extract the logs you will need to use "logcat" command as an adb command or directly in a shell prompt of your emulator or connected device:
adb logcat -v time > applog.txt
If you have any other questions about the Play Console, please let me know and I will be happy to assist you further.
Have a great day.
Regards,
Sabrina.
Google Play Developer Support.

Google Play Billing method "queryPurchaseHistoryAsync" not getting all purchases

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.

How to remove test IAP purchase from Android Google Play

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.

Android IABv3 getSkuDetails not returning Sku Details

I am currently battling with Android Iab v3.
I have previously been using the IabHelper class from Google to display available products with success. However, today it is no longer returning me anything.
The content of the querySku field passed to the getSkuDetails function of the IInAppBillingService in the IabHelper class is:
Bundle[{ITEM_ID_LIST=[com.app.android.credits.10,
com.app.android.credits.25, com.app.android.credits.50]}]
What I get back in the bundle it return is:
Bundle[{DETAILS_LIST=[], RESPONSE_CODE=0}]
Since the time it has worked and now I have not altered the IabHelper code at all along with the code that displays the products. The important bit of code being:
private void getItemsForSale()
{
ArrayList<String> skuList = new ArrayList<String>();
skuList.add(getResources().getString(R.string.ten_credits_product_id));
skuList.add(getResources().getString(R.string.twenty_credits_product_id));
skuList.add(getResources().getString(R.string.fifty_credits_product_id));
mHelper.queryInventoryAsync(true, skuList, this);
}
#Override
public void onQueryInventoryFinished(IabResult result, Inventory inv)
{
if(result.isFailure())
{
Log.d("DEBUG", "Error Inventory Query: " + result);
AppMsg.makeText(BuyCreditsActivity.this, R.string.sorry_something_went_wrong, AppMsg.STYLE_ALERT).show();
}
else
{
// Code here queries the inv object returned which has a blank array
}
}
This is why I am so confused.
Does anyone have any idea as to what external factor might have caused this to start not returning any product details?
I'm getting the same empty bundle since yesterday. This question is not the same thing, but it was asked recently, and seems to be related: Android inventory.getSkuDetails() returning null
(I have experienced this one too about 2 weeks ago)
First answer states:
"they say they have made changes which requires the apk to be published before adding in app purchases they recommend uploading the apk to alpha testing channel and published (not in draft mode)."
I will try this, perhaps it solves the problem.
Update: I published the app, but the developer console wrote that the modifications does not take effect immediately. After some hours the problem existed, but today it works well.
I had this same problem. In my case the following worked:
Make sure it's actually published in alpha, beta, or production.
If it appears, for example, as "Draft in Alpha" or beta then you
haven't published in. Publishing means that you also need to upload
the minimum required assets for Google Play, descriptions, content
ratings, etc (See the APK, Store Listing, and Content Rating tabs in
the developer console).
At least for alpha and beta testing, there is a list of testers that
you must create and add each tester's email. Look for the "Manage
Testers" section in the Alpha Testing or Beta Testing tab.
Each tester must also accept to be a tester. There is an opt-in URL
link which you must send to your testers, and they must click on the
link and accepts the conditions.
After all that is done, you still have to wait about 1 hour before
the purchase details start to appear.
Hope this helps!

Categories

Resources