Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Detect refunded managed in-app purchase android IAP 2.0.3

I'm having trouble figuring out how to detect when a refund has been issued for a managed (uncomsumable) in-app product in Android using com.android.billingclient:billing:2.0.3. The problem seems fairly deep though maybe I'm making it more complicated than it ought to be.

To begin, I've made a test purchase which has been acknowledged AND refunded:

enter image description here

Looking at the logs of my app I see the following:

D/BillingManager: Got a verified purchase: Purchase. Json: {"orderId":"GPA.3362-7185-5389-78416","packageName":"com.glan.input","productId":"pro","purchaseTime":1567672759460,"purchaseState":0,"purchaseToken":"pbkpcaadklleoecegjfjdpbl.AO-J1OwsR6WVaVZCCYOU6JyYN1r0qJsrwitIPZfhc3jX4yketRUwNzKqwMgYx0TgZ2GebEGbXDL0RlMyogwtSKSPsaHCJ4RA4MPlIGay-aM1-QhmnqwjXjQ","acknowledged":true}
I/BillingManager: purchase pbkpcaadklleoecegjfjdpbl.AO-J1OwsR6WVaVZCCYOU6JyYN1r0qJsrwitIPZfhc3jX4yketRUwNzKqwMgYx0TgZ2GebEGbXDL0RlMyogwtSKSPsaHCJ4RA4MPlIGay-aM1-QhmnqwjXjQ is in 1 state

There's something funny going on here:

  1. We can see the order IDs match up between what's in the image and the detected purchase
  2. The first log line is printing the purchase with Log.d(TAG, "Got a verified purchase: " + purchase); which is printing the underlying JSON which represents the purchase.
  3. Note that "purchaseState":0
  4. The second log line is issued with Log.i(TAG, "purchase " + purchase.getPurchaseToken() + " is in " + purchase.getPurchaseState() + " state");.
  5. Note that here purchase.getPurchaseState() is resulting in a value of 1

If I look at the implementation of getPurchaseState in Android Studio I see the following:

  public @PurchaseState int getPurchaseState() {
    switch (mParsedJson.optInt("purchaseState", PurchaseState.PURCHASED)) {
      case 4:
        return PurchaseState.PENDING;
      default:
        return PurchaseState.PURCHASED;
    }
  }

Earlier in the file the PurchaseState interface is declared as:

  @Retention(SOURCE)
  public @interface PurchaseState {
    // Purchase with unknown state.
    int UNSPECIFIED_STATE = 0;
    // Purchase is completed.
    int PURCHASED = 1;
    // Purchase is waiting for payment completion.
    int PENDING = 2;
  }

It seems like getPurchaseState never returns PurchaseState.UNSPECIFIED_STATE and only returns PENDING which the JSON comes with a value of 4. I've confirmed that a state of PENDING is correctly returned when the purchase is performed with a payment method that takes a while to approve.

I've found posts like In-App Billing v3 - Don't detect refund which suggest that Play Services are caching purchases but I'm not convinced that's causing this problem because if I modify my code betweens runs of my app to acknowledge/consume the purchase those get state changes get immediately reflected in the JSON of the purchase.

How am I supposed to detect a refunded managed product?

like image 802
Paymahn Moghadasian Avatar asked Sep 05 '19 09:09

Paymahn Moghadasian


People also ask

How do I check in app purchases on Android?

When you are testing in app purchases on Android, you must use a signed version of the app. Your Google Play email must be listed in the Google Play Console as a tester. The easiest way to do this is to publish your app to a Beta release with open testing.

Will Google refund in app purchases?

User returns a paid app: After purchasing a paid app, a user has up to 2 hours to return it for a full refund. They can only return an app once. If they purchase the same app again, they won't be able to return it a second time. User requests a refund: Users can request a refund on Google Play.

How many times can you refund on Google Play?

You can return most books bought on Google Play for a full refund within 7 days of purchase. If they are defective, unavailable, or don't perform as stated, you can request a refund at any time. If you return a book it may be removed from your library and you may not be able to read it. Single issues.


1 Answers

I have one purchase (SkuType.INAPP) in my application. I make a test purchase and then make a refund.

Problem:

purchase.getOriginalJson()  // contains "purchaseState":0
purchase.getPurchaseState() // returns 1

Inside com.android.billingclient.api.Purchase:

public int getPurchaseState() {
    switch(this.zzc.optInt("purchaseState", 1)) {
    case 4:
        return 2;
    default:
        return 1;
    }
}
//...
public @interface PurchaseState {
    int UNSPECIFIED_STATE = 0;
    int PURCHASED = 1;
    int PENDING = 2;
}

Hacky way to check purchaseState from original json:

purchase.getOriginalJson().contains(String.format("\"purchaseState\":%s", Purchase.PurchaseState.PURCHASED))

Unfortunately, this problem still exists!

More details here.

like image 61
kitfist0 Avatar answered Sep 30 '22 16:09

kitfist0