What result codes can be returned from BillingClient.launchBillingFlow()?
Asked Answered
H

2

6

I'm using using Google's Billing Library 4. The documentation for BillingClient.launchBillingFlow says:

Initiates the billing flow for an in-app purchase or subscription.

It will show the Google Play purchase screen. The result will be delivered via the PurchasesUpdatedListener interface implementation set by BillingClient.Builder.setListener(PurchasesUpdatedListener).

The PurchasesUpdatedListener is passed a BillingResult object that contains a response code. However, the launchBillingFlow method also returns a BillingResult object. My question is, what response codes do I need to deal with in each place?

The documentation is, shall we say, less than clear, and also seems wrong. For launchBillingFlow, it says that the method returns a BillingResult with a code of BillingResponseCode.BILLING_CANCELED if the user cancels the purchase flow. However, my experiments show that code is actually delivered in a call to PurchasesUpdatedListener.onPurchasesUpdated.

Unfortunately, the source code for BillingClient isn't available, but I did decompile the library module. As far as I can tell, the call to launchBillingFlow can return the following result codes:

  • OK - when the flow successfully starts (and the user is shown Google's purchase screen).
  • SERVICE_DISCONNECTED - when the BillingClient isn't currently connected to Google Play on the device.
  • SERVICE_TIMEOUT - if the connection breaks during the attempt to launch the flow. (I think I read somewhere that this can happen if Google Play is being updated in the background.)
  • FEATURE_NOT_SUPPORTED - under various conditions where the BillingFlowParams don't match the current configuration of the BillingClient object.

There also seems to be a place in the decompiled code where certain error responses from the Google Play billing service are captured and returned here. Does anyone know what other response codes can be returned from a call to launchBillingFlow?

Hulton answered 17/8, 2021 at 23:30 Comment(11)
Actually you can ignore result return when you call launchBillingFlow, the result OK here means the app starting purchase follow successfully. You should focus on the result pass to onPurchasesUpdated method. this is final result when user purchased or cancelled or already owner of item...Shifflett
@CôngHải - It doesn't seem right to simply ignore it. It's clear from what I can decipher from the decompiled code that the purchase flow can fail to start, and my code should be aware of when that happens and react differently based on the reason for the failure.Hulton
The bizarre thing is that launchBillingFlow is not modal. Therefore the CANCELLED return value doesn't make sense. The user may see the dialog and press the back button, but the return from launchBillingFlow() has already happened before that. I'm struggling to understand how to incorporate this new version of the Billing Library into my app - as the dialog is not modal you cannot know if the user pressed back button or continued. You cannot perform UI changes from onPurchasesUpdated (and would not know if it was cancelled either).Famagusta
Ignore the last part of what I said. The USER_CANCELED gets sent through to onPurchasesUpdated() and you can handle UI from there.Famagusta
@Famagusta - Yes, that's the point of my question. What result codes need to be handled when launchBillingFlow returns and what codes need to be handled in onPurchasesUpdated()? There's obviously some overlap, but there's also obviously some codes that are unique to each context. The documentation just lists all possible return codes, without distinguishing which should be handled where.Hulton
Hey TedHopp, @cbn! Did you guys ever found the answer to this? We're also updating to v4 now and double-checking every single call and callback. I find it very, very odd. If anything, I guess we'll handle any unsuccessful error code as a "generic" error, notify the user and hit some analytics. It'd be great if any Google engineers could chime in!Katzman
@Katzman - Nope. We're just making assumptions about what codes to expect where and treating anything that violates the assumptions as an unknown error.Hulton
Thanks TedHopp! :) We'll carry on with the same strategy, then. If I ever manage to get feedback from the Google team on this subject, I'll definitely share it here :DKatzman
@Katzman -- 👍!Hulton
I'm back here again after all this time. I've been Googling for an answer and found this again. IAB is a bit ridiculous. The immediate result may not even be worth dealing with. For instance, if there is no network (eg: airplane mode) then IAB will return an immediate result of network error - BUT it will also internally pop up a dialogue to announce this. So what do you do? If your app pops up a dialogue it's a duplicate. Some errors may be handled internally and some not. Still no documentation on this.Famagusta
Just an additional comment on this. On my devices (both old and new Android) if the device is in landscape and is in airplane mode then the immediate return is OK and, bizarrely (stupidly!) the listener is never called! What kind of weirdness is this??? This is with IAB version 6.0. You'd think they'd have got this sorted out by now.Famagusta
F
3

As per the my experience you can handle the result like mentioned below example :-

 this.mBillingClient = BillingClient.newBuilder(this).setListener(new PurchasesUpdatedListener() {
    @Override
    public void onPurchasesUpdated(BillingResult billingResult, @Nullable List<Purchase> list) {
      if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && list != null) {
        for (Purchase purchase : list) {

          if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
            addPurchaseDetails(purchase);
            handlePurchase(purchase);

          }
        }
      } else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.USER_CANCELED) {
        Log.v("purchase", billingResult.getDebugMessage());
      }

Means you have to check :- billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && list != null

And get the Purchase from the list and handle the purchase as per your need.

    for (Purchase purchase : list) {
       }

Also find the errors below :-  

public @interface BillingResponseCode {
        int SERVICE_TIMEOUT = -3;
        int FEATURE_NOT_SUPPORTED = -2;
        int SERVICE_DISCONNECTED = -1;
        int OK = 0;
        int USER_CANCELED = 1;
        int SERVICE_UNAVAILABLE = 2;
        int BILLING_UNAVAILABLE = 3;
        int ITEM_UNAVAILABLE = 4;
        int DEVELOPER_ERROR = 5;
        int ERROR = 6;
        int ITEM_ALREADY_OWNED = 7;
        int ITEM_NOT_OWNED = 8;
    }
Foregoing answered 1/9, 2021 at 12:47 Comment(3)
Yes, of course I can handle all errors in a catch-all else branch. However, my question was about what errors to expect in each situation (launching the billing flow or processing the result of a billing flow).Hulton
You can check the error like (billingResult.getResponseCode() == BillingClient.BillingResponseCode.XXX). XXX will be public @interface BillingResponseCode { int SERVICE_TIMEOUT = -3; int FEATURE_NOT_SUPPORTED = -2; int SERVICE_DISCONNECTED = -1; int OK = 0; int USER_CANCELED = 1; int SERVICE_UNAVAILABLE = 2; int BILLING_UNAVAILABLE = 3; int ITEM_UNAVAILABLE = 4; int DEVELOPER_ERROR = 5; int ERROR = 6; int ITEM_ALREADY_OWNED = 7; int ITEM_NOT_OWNED = 8; }Foregoing
Please read my question again. I'm aware of all the possible values. However, not all values can be returned in all places. For instance, USER_CANCELED logically cannot be returned from launchBillingFlow() because if that call fails, the user has not even seen the billing screen to have the opportunity to cancel. The question is not what are the possible error codes; rather it is, which errors might be returned from which API calls?Hulton
B
0

First of all, thank you for your good question. As far as I researched, the values ​​listed below are available.

BillingClient.BillingResponseCode // all codes

int BILLING_UNAVAILABLE Billing API version is not supported for the type requested.

int DEVELOPER_ERROR Invalid arguments provided to the API.

int ERROR Fatal error during the API action.

int FEATURE_NOT_SUPPORTED Requested feature is not supported by Play Store on the current device.

int ITEM_ALREADY_OWNED Failure to purchase since item is already owned.

int ITEM_NOT_OWNED Failure to consume since item is not owned.

int ITEM_UNAVAILABLE Requested product is not available for purchase.

int OK Success.

int SERVICE_DISCONNECTED Play Store service is not connected now - potentially transient state.

int SERVICE_TIMEOUT The request has reached the maximum timeout before Google Play responds.

int SERVICE_UNAVAILABLE Network connection is down.

int USER_CANCELED User pressed back or canceled a dialog.

launchBillingFlow // return values for

The result of the launch billing flow operation. BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED if the user already owns the item being purchased, BillingClient.BillingResponseCode.ITEM_UNAVAILABLE if the item is not available to be purchased, and BillingClient.BillingResponseCode.USER_CANCELED if the user dismissed the purchase flow.

Update Answer

what are the possible return values when the call to launchBillingFlow() fails

  • ITEM_ALREADY_OWNED
  • ITEM_UNAVAILABLE
  • USER_CANCELED

what are the possible values when onPurchasesUpdated() is called (at the completion of the flow).

  • OK
  • ITEM_UNAVAILABLE
  • ITEM_ALREADY_OWNED
  • USER_CANCELED
  • ERROR launchBillingFlow Error
Briny answered 31/8, 2021 at 11:10 Comment(3)
You've just listed all the possible values for BillingResponseCode. However, that wasn't my question. The issue is: what are the possible return values when the call to launchBillingFlow() fails and what are the possible values when onPurchasesUpdated() is called (at the completion of the flow). For instance, it is logically impossible for USER_CANCELED to be returned from launchBillingFlow() because if that call fails, the user has not even been presented with the opportunity to cancel the purchase.Hulton
I updated my answer. I suggest you specifically check the ERROR status, as the callback will be called in any case.Rem
Please explain the source for your modified answer, which I think cannot be right. For instance, how could USER_CANCELED possibly be returned from launchBillingFlow()? If the call fails, then the billing flow has not been presented to the user and the user has had no opportunity to cancel.Hulton

© 2022 - 2024 — McMap. All rights reserved.