Search Unity

Bug Google Play refunded products purchased status are incorrect

Discussion in 'Unity IAP' started by xLeo, Apr 15, 2022.

  1. xLeo

    xLeo

    Joined:
    Sep 21, 2010
    Posts:
    194
    Google Play's refunded IAPs are not returning the correct values through GooglePlayReceipt.purchaseState property.

    To reproduce it I have:
    • Purchased some IAPs a few months ago;
    • Refunded them through Google Play Console (also, a few months ago);
    • I cast IPurchaseReceipt to GooglePlayReceipt;
    • GooglePlayReceipt.purchaseState property returns Purchased
    • Using Postman, I check the purchaseState through Google Play's API and get the following result:
    upload_2022-4-14_23-21-57.png

    Please note that "purchaseState": 1 which means that the purchase is Canceled, according to the API mentioned above.

    A few extra things to mention:
    • I have refunded those IAPs months ago, and have rebooted my device several times;
    • I also uninstalled our app just before testing this issue and have cleaned my user data on the Backend;
    • The app has been uninstalled many times over these months.
    Is the purchase status of a given IAP cached on the user's device? If so, how can I clean that cache?
    Does Unity IAP check with Google Play servers if the purchase is valid at least once? If not, is that a planned feature?
     
  2. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
  3. xLeo

    xLeo

    Joined:
    Sep 21, 2010
    Posts:
    194
    That's weird, based on this documentation purchaseState = 1 is Canceled:
    upload_2022-4-16_19-44-10.png

    These are testing purchases, but we have received reports (from Google Play team) that our app is not taking refunds into consideration.

    FYI, this is the current situation:
    • Google use a non-consumable IAP (defined by us) to tag an user as a Google Play Pass subscriber or not. When the user cancel its subscription, the non-consumable IAP should not be considered anymore;
    • We used to have non-consumable IAPs in our app, which are not accessible anymore. The problem is that if someone purchased it and got refunded, it's stuck with it forever. My user, for example, is stuck 3 non-consumable IAPs and I would like to test the regular app flow (without any previously purchased SKU);
    • Subscriptions refunds seem to expire as intended, but I'm not 100% sure if refunds work (probably not).

    The latest test I have performed (mentioned in this post) is using Unity IAP v4.1.4

    Since we are on this topic, it seems that 2 of non-consumable products are refunded on Google Play, but one is not. Problem is: it is not possible to refund the product, since a long time has passed. Is there any API to "consume a non-consumable product"? (Or revoke the product, as you mentioned)

    Point is: I have noticed this issue a while ago and expected it to be fixed. We have not give it much attention because our app is still small and we have been saving up on marketing budget, but it is starting to be a problem. :)
     
  4. Baroni

    Baroni

    Joined:
    Aug 20, 2010
    Posts:
    3,264
    @xLeo You are referring to the server-side validation API, while Jeff was referring to the client API. There are indeed differences between them regarding the purchaseState value, so you should make sure to use the correct API depending on your use case (local or server validation). I can confirm that on the server-side API, value 0 is active and 1 is cancelled.

    Unfortunately when using server-side validation, Google does not fire a webhook call or anything like that to be informed of refunded / revoked purchases of non-consumables. However, they do for subscriptions. This means there is basically no chance to find out whether a purchase has been refunded, just by validating its receipt again. In the client API and when using Unity IAP, the product's receipt will get removed after the device renews its inventory cache. This can take a day or two. You are then able to check product.hasReceipt.

    The option to refund+revoke (i.e. completely remove a product from a user) is in the Google Play Console, section order management. There is a refund button on each transaction and when clicking it, you can toggle "Remove entitlement" as well.

    P.S. if you are concerned about purchase security, I would like to suggest taking a look at my Receipt Validator service and Unity asset, which comes with a free plan too.
     
    Endahs likes this.
  5. DankalApps

    DankalApps

    Joined:
    Mar 8, 2023
    Posts:
    17
    So, if I want to test the in-app purchases, related UI, unlocks, etc. I have to wait "day or two" each time I want to remove/refund purchases?

    I did this recently in Kotlin app and this was maybe not immediate, but reasonably fast (can't say how long it took, it just had not become such an inconvenience). So it is possible to do.. but is it possible to do in Unity?
     
  6. Baroni

    Baroni

    Joined:
    Aug 20, 2010
    Posts:
    3,264
    @DankalApps Pressing refund+revoke in the Google Play Console should refresh the user inventory cache quite fast. Additionally you can call UnityPurchasing.ClearTransactionLog in your app to wipe all existing transactions, pending or not. The link refers to legacy documentation but the method still exists and works. When restarting your app afterwards the purchases should be deleted.
     
  7. DankalApps

    DankalApps

    Joined:
    Mar 8, 2023
    Posts:
    17
    @Baroni Thank you and sorry for late response, was fighting with Google Play Game Services...

    I refunded a product over month ago and still `product.hasReceipt` is true.

    Made sure that there is status "Refunded" on Google Console -> Order Management.
    Made sure receipt ID matches ID of Refunded purchase at Google Console.

    Then I do double check:
    Code (CSharp):
    1.  
    2. if (product.hasReceipt) {
    3.      if (googleReceipt.purchaseState == GooglePurchaseState.Purchased) {
    4.          (...)
    5.      }
    6. }
    7.  
    And it passes both checks :(

    Tried to uninstall, then install app, did not help.
    UnityPurchasing.ClearTransactionLog did not help either.

    You mention something called revoke, but I do not see such an option now. Google Console just says purchase is returned for what its worth.

    It might be problem at Google side, not Unity. I can't say for sure what is causing this.

    EDIT: When I try to repurchase the exact product, I get "You already own this product" dialog.

    EDIT2: Tried other IAP, and found "Remove entitlement" option (probably that's what you meant by "revoke"). When I clicked that, I got the item refunded/revoked/deleted/etc. immediately and could buy it again after a minute.

    It is gonna be hard to do it for the first product though for which "remove entitlement" was not checked. Found answer for Java only:
    https://stackoverflow.com/questions...efund-a-test-order-in-app-purchase-in-play-co
    Maybe the best option is to remove IAP and add it again
     
    Last edited: Jul 3, 2023
    alliexpdx likes this.
  8. Baroni

    Baroni

    Joined:
    Aug 20, 2010
    Posts:
    3,264
    Yes, that's what I meant - Remove entitlement :)
    I do not know how to do that afterwards though, after doing a refund already. If your app is not live yet, removing the IAP completely could be worth a try.
     
  9. alliexpdx

    alliexpdx

    Joined:
    Jul 24, 2022
    Posts:
    5
    I was just having this issue as well after waiting over 72 hours and not being able to remove my refunded non-consumable IAP orders/entitlements.

    I ended up using this posts link above on stack overflow thanks @DankalApps, but I used another solution on the page (i.e. not the Java one), so it's a code-less solution if you are looking for that.

    The last solution on that page (currently at least) was to use the GoogleAPI to consume it:
    https://developers.google.com/android-publisher/api-ref/rest/v3/purchases.products/consume

    I simply pasted info into the web form for the previously cancelled order.
    [Order Management]:Select Order Details arrow of refunded order)::[Copy Purchase token] & (other known data product id & package name) of the canceled order into the form.
    pressed [Execute]
    Received a code 1.1 204 (Content success)
    Restarted my app and the purchases were reset immediately.

    Tried the purchase again, and I was able to retest the non-consumable purchase again.
     
    Nelxi likes this.
  10. uncolike

    uncolike

    Joined:
    Jan 17, 2020
    Posts:
    31

    Thanks! It works