Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

IAP Android with refunds

Discussion in 'Unity IAP' started by drizztfun, Apr 8, 2018.

Thread Status:
Not open for further replies.
  1. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
  2. wcoastsands

    wcoastsands

    Joined:
    Sep 30, 2011
    Posts:
    44
    Turns out I missed a step in the refund process. There is a revoke option for non-consumable IAP products. However, receipt validation still results in GooglePurchaseState.Purchased.

    Only after clearing app data and com.android.vending is the product no longer listed among SKUs owned, which then allows for the IAP non-consumable product to be purchased again.

    I'm already calling UnityPurchasing.ClearTransactionLog() before restoring transactions. Am I overlooking something that would allow me to effectively pull the latest SKUs, or force vending to update?

    Thanks in advance,

    Nikko
     
  3. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    What IAP Version? Please wait 24 hours for your test.
     
  4. wcoastsands

    wcoastsands

    Joined:
    Sep 30, 2011
    Posts:
    44
    We're using the latest (UnityIAP Version: 1.23.5).

    Waiting 24hrs may not be a viable solution. Google has responded with the following:

    "Developers can test revocation by making an IAP purchase, refunding the purchase via play console, and then ensuring that the item is revoked from the client upon next launch."
     
  5. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Sorry 24 hours is not a solution, it's only a test. It may behave much sooner in a live app. Their receipts may be delayed, we have no control over that. How do they specify to "ensure the item is revoked"? You might also try bool IGooglePlayConfiguration.aggressivelyRecoverLostPurchases

    ## [1.23.5] - 2020-08-12
    ### Fixed
    - GooglePlay - Fixed `IGooglePlayConfiguration.aggressivelyRecoverLostPurchases == false` (default) to reward players for currently in-flight purchases only, and not historical purchases, when the player cleans their device's TransactionLog, starts and cancels a purchase, and restarts the app.
     
  6. wcoastsands

    wcoastsands

    Joined:
    Sep 30, 2011
    Posts:
    44
    This is the response we got from Google:

    “Remember for a free app with IAPs, the thing to check for would actually be getPurchases() and other PBL/IAP-based methods. This will return a list of user purchased IAPs.”

    Is there an equivalent method that can be called through Unity IAP?

    Currently we call RestoreTransactions() at the start of each session. Which triggers ProcessPurchase() for each owned skew. Receipt validation is then performed, and the state of the purchase is returned as Purchased instead of Refunded.
     
  7. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    You shouldn't need to call RestoreTransactions, the receipts should already be available in your product controller during initialization. Please try the setting mentioned and check to see if the receipts are then properly refreshed. There is no explicit method as you suggest.
     
  8. wcoastsands

    wcoastsands

    Joined:
    Sep 30, 2011
    Posts:
    44
    We’re calling RestoreTransactions() to unlock items based on product purchases. So as it calls ProcessPurchase() for each SKU owned, we unlock the item in game if receipt validation checks out (doesn’t throw a security exception, and state is Purchased). This is done in the first scene after the application finishes loading.

    I lost you in the second part. Which setting are you referring to?

    Thanks.
     
  9. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    You should not need to depend on ProcessPurchase, you should just inspect the receipts during IAP initialization by iterating over the products in your controller in a foreach loop. I'm referring to the setting here where we more aggressively call GetPurchases https://forum.unity.com/threads/iap-android-with-refunds.525835/page-2#post-6254934
     
    MonsterPlanet1 and wcoastsands like this.
  10. wcoastsands

    wcoastsands

    Joined:
    Sep 30, 2011
    Posts:
    44
    Thanks for the suggestions, Jeff.

    I enabled the configuration option to aggressively recover lost purchases:
    Code (CSharp):
    1. builder.Configure<IGooglePlayConfiguration>().aggressivelyRecoverLostPurchases = true;
    And am retrieving and validating the receipt for products from the store controller after initialization, but still seeing the same results:
    • Purchase is refunded and revoked in Google Play Console.
    • Product is found in the the store controller, and has a receipt.
    • Receipt is validated and still has the state of Purchased.
    This was all done within an hour. Allowing some more time for the refund to propagate before testing again.
     
    JeffDUnity3D likes this.
  11. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Does a Restore trigger the ProcessPurchase for this item still also? Do you have a second Android device to test? Restore happens automatically when uninstall/reinstall, you don't need to explicitly call it (although you can) so you can test on the same device too.
     
  12. wcoastsands

    wcoastsands

    Joined:
    Sep 30, 2011
    Posts:
    44
    For testing purposes, I am still explicitly calling RestoreTransactions(), after validating receipts for products in the store controller once initialization is complete. Currently, both are mirroring the same results.

    I don't have a second device handy at the moment. Using the same device:
    • Clearing app data still results in receipt validating with Purchased state.
    • Uninstall and reinstall the app still results in receipt validating with Purchased state
     
  13. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    So to be clear, if it's still triggering ProcessPurchase during Restore, it's definitely a Google issue at this point. They should not be restoring a refunded item.
     
  14. wcoastsands

    wcoastsands

    Joined:
    Sep 30, 2011
    Posts:
    44
    Is there a way to confirm this is the case?

    There's still the case that clearing vending results in no products being listed as owned.
    Code (bash):
    1. adb shell pm clear com.android.vending
    My suspicion is that the receipts are being cached locally, and just aren't being updated to reflect the refund.
     
  15. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    No, the ProcessPurchase is a direct API callback via the Google billing library. It should not be triggering at all. I suspect ProcessPurchase is triggered when you perform that clear? At any rate, that's Google (90% confidence :) )
     
    Last edited: Aug 28, 2020
  16. wcoastsands

    wcoastsands

    Joined:
    Sep 30, 2011
    Posts:
    44
    Thanks, Jeff. I appreciate it.
     
  17. wcoastsands

    wcoastsands

    Joined:
    Sep 30, 2011
    Posts:
    44
    Hey, Jeff. I'm still working to resolve this issue. I've updated to UnityIAP v2.0.0.

    When canceling a purchase, the ProcessPurchase callback is being called after the OnPurchaseFailed callback. The problem is that the canceled purchase is issued a receipt, and validates as Purchased as opposed to Canceled.

    Reviewing the content of the receipt, I don't see any indication in the JSON data as to whether the receipt status is Purchased, Canceled, or Revoked. How are we supposed to determine how to process the purchase when ProcessPurchase is called?

    Update: It looks like the difference in JSON data is that a Canceled receipt is lacking fields for orderId, packageName, and purchaseState. So now wondering, how is the value of UnityEngine.Purchasing.Security.GooglePlayReceipt.purchaseState determined?

    Update 2: Since the packageName field is missing from Canceled receipts, I'm checking the value of GooglePlayReceipt.packageName before GooglePlayReceipt.purchaseState. Which gives me more reliable results.

    I believe this is a bug in UnityIAP. If the purchaseState is not set in the receipt, it should not default to the default enum value of GooglePurchaseState.Purchased (= 0), but to GooglePurchaseState.None (= -1).
     
    Last edited: Sep 5, 2020
  18. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    We are aware of the issue with cancelling the purchase and is a separate issue from the Refund question discussed here.
     
  19. wcoastsands

    wcoastsands

    Joined:
    Sep 30, 2011
    Posts:
    44
    I don't believe it's entirely unrelated. We're still talking about how ProcessPurchased is called and how receipts are validated. If the receipt is present after revoking a product, we'll still get the same results.

    Google provided steps for evaluating the refund/revoke process:
    1. Clear cache & storage of the Play Store.
    2. Install the game and progress to the IAP upsell screen.
    3. Purchase the product via IAP.
    4. Observe the user can play through content previously restricted by the paywall.
    5. Close the game and remove the application from the background.
    6. Wait for payment status of the IAP purchase in Google Play Console to be "Charged".
    7. Select "Refund" and enable the option "Revoke" before submitting the refund.
    8. Wait for payment status of the IAP purchase in Google Play Console to be "Refunded".
    9. Clear cache & storage of the Play Store.
    10. Launch the game, and attempt to access content normally restricted by the paywall.
    11. Observe the IAP upsell screen.
    12. Navigate back to the main menu using the navigation option located at the bottom of the upsell screen.
    13. Attempt to access chapter 2.
    14. Observe the IAP upsell screen.
    I was able to go through these steps without issue. However, Google was experiencing the issue described when testing Play Pass licensing that auto-grants the IAP. After canceling the Play Pass license, they were still seeing the same issue that I was able to reproduce by canceling the purchase.

    The issue is that if a receipt exists without purchaseState, it should not validate as Purchased.
     
    Superdeath likes this.
  20. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    I'm not disagreeing, there appears to be an issue with refunds. But cancelling the purchase dialog is a completely separate problem. It of course would lead to the same refund issue, but is unrelated.
     
    Last edited: Sep 8, 2020
  21. wcoastsands

    wcoastsands

    Joined:
    Sep 30, 2011
    Posts:
    44
    Just a quick update on this, checking if packageName is null or empty before checking the purchaseState resolved the issue. Submission was approved by Google.
     
    Superdeath and JeffDUnity3D like this.
  22. xLeo

    xLeo

    Joined:
    Sep 21, 2010
    Posts:
    193
    @JeffDUnity3D Is there an ETA for Google Play refunds/cancelations support?

    I have just checked with a test purchase, I get a "Purchased" purchase state on Unity app ("purchaseState":0 on Receipt), but by checking on Google Play receipt validation service I get a "purchaseState": 1 (cancelled), which is correct.

    Besides Google Play, should I worry about iOS cancelations as well?
    What about Subscriptions? Do they work correctly?

    My main question is: will this be released soon (within the next month or 2)? Or should I implement a server-side validation?
     
  23. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Hopefully within 2 months
     
    xLeo likes this.
  24. DidzTM

    DidzTM

    Joined:
    Jan 30, 2018
    Posts:
    17
    Hi,
    It's solved at this time ! With the revoke option in the google console when you make a refund. (you just must wait some hours)
     
    JeffDUnity3D likes this.
  25. DustyDev

    DustyDev

    Joined:
    Sep 23, 2015
    Posts:
    40
    Just wanted to check that the code I wrote is an expected way to verify if a receipt is good (to include after a user gets a refund and access is revoked)

    This is called for every Play receipt. The receipt has already been run through detangling with Validator.Validate().
    Code (CSharp):
    1.     private bool IsValidGoogleReceipt(GooglePlayReceipt receipt)
    2.     {
    3.         if (receipt == null)
    4.         {
    5.             return false;
    6.         }
    7.         if (receipt.packageName.IsNullOrEmpty()) // Can happen for refunded item
    8.         {
    9.             return false;
    10.         }
    11.         Debug.Log(receipt.purchaseState);
    12.         Debug.Log(receipt.purchaseToken);
    13.         Debug.Log("Receipt for product " + receipt.productID + " has purchase state of: " + receipt.purchaseState);
    14.         if (receipt.purchaseState != GooglePurchaseState.Purchased)
    15.         {
    16.             return false;
    17.         }
    18.         return true;
    19.     }
    Also both apple and google play receipts have this to confirm they are for products we are selling:

    Code (CSharp):
    1. if (m_Controller.products.WithID(productReceipt.productID) == null)
    2.                 {
    3.                     // Don't have a product with that ID, invalid receipt
    4.                     Debug.LogWarning("Receipt for product " + productReceipt.productID + " is for a product we are not selling!");
    5.                     return false;
    6.                 }
    Does this appear to be correct? If so, my receipts are still saying they are valid after the refund/revoke process. The purchase state is "Purchased".
     
  26. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Looks correct at first glance. How long did you wait?
     
  27. DustyDev

    DustyDev

    Joined:
    Sep 23, 2015
    Posts:
    40
    I did the refund Monday afternoon and just tested again. Also, I paid real money, and did a real refund/revoke for the purchase (even though the app is still in Closed Beta testing). Using IAP 2.1.1 / Unity 2019.4.13f1
     

    Attached Files:

  28. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    We are passing through what Google sends to us. As mentioned previously, as a test, try initiating a Restore, you should not expect to see ProcessPurchase. Restore happens automatically on Android if you uninstall/reinstall. Google should not restore products if they have been refunded.
     
  29. DustyDev

    DustyDev

    Joined:
    Sep 23, 2015
    Posts:
    40
    I have restored, and installed on a second device, but am still getting it as purchased.
     
  30. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Would you agree then that it appears to be a Google issue?
     
  31. DustyDev

    DustyDev

    Joined:
    Sep 23, 2015
    Posts:
    40
    I would, but I'm not sure how you go from the returned JSON to the enum in Unity. Looks like the first step is done in Java at:

    Code (CSharp):
    1. this.mPurchaseState = o.optInt("purchaseState");
    the int is then somehow converted to the GooglePlayPurchaseState. It's tough to follow these things in the tools available. Either way, I'm not sure what the payload in JSON looks like (as I'm not sure what version of billing or whatever Unity IAP is using at the moment). Is it possible the JSON int and the Unity IAP enum are not sync'd in that the API has changed and the returned int means something different? I can only assume you have tests for these sorts of things, but I'm just trying to explore the more likely problems than Google having messed up (not a Google fanboy, but I'd assume more people would be complaining about this if it were a problem with anything other than Unity IAP). I've found a few potentially relevant threads from StackOverflow on the subject:

    https://stackoverflow.com/questions...d-or-refunded-order-for-google-in-app-billing
    https://stackoverflow.com/questions...play-in-app-billing-version-3-support-refunds
    https://stackoverflow.com/questions...lling-version-3-server-changes-made-available
     
    Last edited: Oct 28, 2020
  32. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    If Google is restoring a product and you receiving the ProcessPurchase callback at all, then it's not being recognized at their end as having a refund in the first place, regardless of any client processing (using my assumptions). If the product is refunded, I wouldn't expect a ProcessPurchase callback at all (and therefore no receipt either). But to your point, you are saying you WOULD expect a ProcessPurchase after a successful refund, and a valid receipt, but with the PurchaseState set accordingly. That is a valid argument, and therefore deserves additional investigation at our end. I will be testing this, and will let you know. What you are seeing could "possibly" be an artifact of beta testing, but again, deserves additional testing here.
     
  33. DustyDev

    DustyDev

    Joined:
    Sep 23, 2015
    Posts:
    40
    Actually, I would NOT expect a ProcessPurchase for a restore. I am verifying the receipt is still valid when the app starts like so:

    In OnInitialized(IStoreController controller, IExtensionProvider extensions) I have this code:
    Code (CSharp):
    1. foreach (var item in controller.products.all)
    2.         {
    3.             if (item.availableToPurchase)
    4.             {
    5.                 Debug.Log(string.Join(" - ",
    6.                     new[]
    7.                     {
    8.                         item.metadata.localizedTitle,
    9.                         item.metadata.localizedDescription,
    10.                         item.metadata.isoCurrencyCode,
    11.                         item.metadata.localizedPrice.ToString(),
    12.                         item.metadata.localizedPriceString,
    13.                         item.transactionID,
    14.                         item.receipt
    15.                     }));
    16. #if INTERCEPT_PROMOTIONAL_PURCHASES
    17.                 // Set all these products to be visible in the user's App Store according to Apple's Promotional IAP feature
    18.                 // https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/StoreKitGuide/PromotingIn-AppPurchases/PromotingIn-AppPurchases.html
    19.                 m_AppleExtensions.SetStorePromotionVisibility(item, AppleStorePromotionVisibility.Show);
    20. #endif
    21.             }
    22.             // Check receipt to make sure it's still valid
    23.  
    24. #if RECEIPT_VALIDATION
    25.             SecurePlayerPrefs.SetBool(item.definition.id, item.hasReceipt && IsValidReciept(item.receipt));
    26. #if UNITY_EDITOR
    27.             if (EDITORONLY_UnlockAllPurchases)
    28.             {
    29.                 SecurePlayerPrefs.SetBool(item.definition.id, true);
    30.             }
    31. #endif
    32. #else
    33.             SecurePlayerPrefs.SetBool(item.definition.id, item.hasReceipt);
    34. #endif
    35.         }
    So I keep the state refreshed locally using playerprefs by checking that each item has a receipt, and that the receipt is valid. The issue is that item.hasReceipt && IsValidReciept(item.receipt)); is returning true for the revoked item, as the code in my last post is telling me the item is still Purchased. My expectation is that IsValidReceipt would return false. For completeness, I'm going to attach all of my code related to that:

    Code (CSharp):
    1.     private bool IsValidReciept(string receiptString)
    2.     {
    3.         try
    4.         {
    5.             var result = Validator.Validate(receiptString);
    6.             Debug.Log("Receipt is initially valid. Contents:");
    7.             foreach (IPurchaseReceipt productReceipt in result)
    8.             {
    9.                 Debug.Log(productReceipt.productID);
    10.                 Debug.Log(productReceipt.purchaseDate);
    11.                 Debug.Log(productReceipt.transactionID);
    12.  
    13.                 GooglePlayReceipt google = productReceipt as GooglePlayReceipt;
    14.                 AppleInAppPurchaseReceipt apple = productReceipt as AppleInAppPurchaseReceipt;
    15.                 if (!IsValidGoogleReceipt(google) && !IsValidAppleReceipt(apple))
    16.                 {
    17.                     return false;
    18.                 }
    19.                 // For improved security, consider comparing the signed
    20.                 // IPurchaseReceipt.productId, IPurchaseReceipt.transactionID, and other data
    21.                 // embedded in the signed receipt objects to the data which the game is using
    22.                 // to make this purchase.
    23.                 if (m_Controller.products.WithID(productReceipt.productID) == null)
    24.                 {
    25.                     // Don't have a product with that ID, invalid receipt
    26.                     Debug.LogWarning("Receipt for product " + productReceipt.productID + " is for a product we are not selling!");
    27.                     return false;
    28.                 }
    29.             }
    30.         }
    31.         catch (IAPSecurityException ex)
    32.         {
    33.             Debug.Log("Invalid receipt: " + ex);
    34.             return false;
    35.         }
    36.         return true;
    37.     }
    38.  
    39.     private bool IsValidGoogleReceipt(GooglePlayReceipt receipt)
    40.     {
    41.         if (receipt == null)
    42.         {
    43.             return false;
    44.         }
    45.         if (receipt.packageName.IsNullOrEmpty()) // Can happen for refunded item
    46.         {
    47.             return false;
    48.         }
    49.         Debug.Log(receipt.purchaseState);
    50.         Debug.Log(receipt.purchaseToken);
    51.         Debug.Log("Receipt for product " + receipt.productID + " has purchase state of: " + receipt.purchaseState);
    52.         if (receipt.purchaseState != GooglePurchaseState.Purchased)
    53.         {
    54.             return false;
    55.         }
    56.         return true;
    57.     }
    58.  
    59.     private bool IsValidAppleReceipt(AppleInAppPurchaseReceipt receipt)
    60.     {
    61.         if (receipt == null)
    62.         {
    63.             return false;
    64.         }
    65.         Debug.Log(receipt.originalTransactionIdentifier);
    66.         Debug.Log(receipt.subscriptionExpirationDate);
    67.         Debug.Log(receipt.cancellationDate);
    68.         Debug.Log(receipt.quantity);
    69.         if (receipt.cancellationDate > DateTime.Now)
    70.         {
    71.             return false;
    72.         }
    73.         return true;
    74.     }
     
  34. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    But you ARE getting a ProcessPurchase! That's a callback from the Google Billing Library, not us. So we are in agreement that for a recognized refund, you would not get such a callback. But since you are, it seems like Google doesn't recognize the refund.
     
  35. DustyDev

    DustyDev

    Joined:
    Sep 23, 2015
    Posts:
    40
    Ah, indeed I am in the case of a restore. So with that thought, and assuming Unity is giving me that because Google Play is giving Unity IAP the restore, now what do I do? I'm assuming there's no Google Developer hotline to call for help!
     
  36. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Use your existing code and publish. It could be the beta testing. A refund is rare, and even so, no harm if the user keeps the product after a refund, they will be a happier customer anyway. Don't make your other 99.9% customers wait for your great game!
     
  37. MianAbdul

    MianAbdul

    Joined:
    Mar 31, 2017
    Posts:
    19
    Screenshot 2020-12-02 at 4.10.59 PM.png
    In your IAP plugin the problem is that payment successfully complete but callback is not properly working in uploaded build and content never open in result of that payment. When your next update comes on and you mentioned in releases on asset-store that the problems are solved and the bugs are fixed.
    Now the new issue is "When the payment successfully complete the content is perfectly opens but on relaunch the game all the receipt always null in `IStoreListener.OnInitialized` and all the subscriptions are canceled due to this reason google-play declined my all IAP purchases".

    Now my questions are:-
    1- What your developer team doing and what your testing team doing?
    2- Who are the responsible for that IAP lose?
     
    Last edited: Dec 3, 2020
  38. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    This thread is dealing with refunds, please stay on topic. You are free to open a new thread. Regarding your firing notice, you will want to test your game before publishing.
     
  39. TranquilBits

    TranquilBits

    Joined:
    Feb 1, 2018
    Posts:
    15
    Hello there.

    I am testing refund scenarios on my NonConsumable purchases and am running into some issues.

    My versions: Unity IAP 2.2.2, Unity package manager In App Purchasing 2.2.1.

    I am using Unity IAP as a store wrapper (Android build now, iOS to follow), and am using a 3rd party cloud provider for data storage and receipt validation.

    For the happy path, I purchase an item, and then ProcessPurchase is called. I send this receipt to my backend which validates the receipt and then I award the product on backend, to be seen via client. After getting confirmation from my backend I set the purchase to confirmed (pending while waiting for backend to validate).

    Then, I start to see strange things. For next launch in Unity IAP OnInitialize, I am iterating over controller.products.all as Jeff mentioned prior in this thread, and I am finding no receipts for any purchases on my NonConsumable products (Product.hasReceipt is false). Also, these products report as availableToPurchase even though they should be singleton products (my understanding).

    Then I refund the item under "Order Management" in the Google Play console, and wait until it states "Refunded" (I also make sure to check "revoke" checkbox.

    On next launch, I get a ProcessPurchase call for my refunded purchase. In the client, my receipt.purchaseState = 0. I pass the call off to my backend which verifies the receipt with Google and I get an error that the purchase was canceled (expected), and the receipt that it passes back to client has purchaseState = 1.

    Now, I could code to this behavior, but is it expected? ProcessPurchase shouldn't be called for canceled transactions right? Furthermore, it seems that Unity IAP's receipt.PurchaseState has an incorrect value for refunded/revoked transactions.

    What is the accepted and "correct" way to handle refunded and revoked purchases using Unity IAP?
     
    Last edited: Dec 2, 2020
  40. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    @TranquilBits Regarding "I am finding no receipts for any purchases on my NonConsumable products". If they are still in Pending, then no receipts would be expected. When IAP initializes, it looks for all products that are still in Pending. You would then be receiving a ProcessPurchase callback at that point for each of those Pending products. You can either return Complete from that ProcessPurchase, or call ConfirmPendingPurchase
     
  41. TranquilBits

    TranquilBits

    Joined:
    Feb 1, 2018
    Posts:
    15
    They should have been confirmed before the specified launch (backend validation is pretty immediate). Is there a way to check if Unity IAP considers transactions pending?
     
  42. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Can you elaborate "should have been"? How are you doing that, are you using ConfirmPendingPurchase? Yes, there is a way to check if Unity IAP considers a transaction pending, ProcessPurchase would be triggered. It's a pass-through API call from Google actually, we are not "checking" per-se.
     
  43. TranquilBits

    TranquilBits

    Joined:
    Feb 1, 2018
    Posts:
    15
    Yes, I am using controller.ConfirmPendingPurchase upon backend confirmation. So if I launch and no ProcessPurchase is called for the transaction I should see a receipt in controller.products.all?

    I will redo the test to confirm.
     
  44. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Yes, based on your scenario, on the next session (and IAP initialization) you should see a receipt and no ProcessPurchase. Please capture the device logs during your tests and attach them here after reviewing them. Your Debug.Log statements will show in the logs, so place debug statements in all your purchase and callback methods https://forum.unity.com/threads/how-to-capturing-device-logs-on-android.528680/
     
  45. TranquilBits

    TranquilBits

    Joined:
    Feb 1, 2018
    Posts:
    15
    After I purchase and then relaunch the application, logcat is showing:

    Code (CSharp):
    1. 12-02 13:15:21.582 21483 21506 I Unity   : Already recorded transaction aodbdhnpbcncmfeikfbafjag.AO-J1OyGMKoM96Txv1VGHXPLljxIhJN9P64VX30g7my-sRgiD5iMUcadZlU1XyOJUB61bAg0ozMPFqbuzx0BJSuPKEIr6s4-2DTPUdsJhcBbbuMJZM3pnW8
    2. 12-02 13:15:21.582 21483 21506 I Unity   : UnityEngine.Purchasing.PurchasingManager:ProcessPurchaseIfNew(Product)
    3. 12-02 13:15:21.582 21483 21506 I Unity   : System.Action:Invoke()
    4. 12-02 13:15:21.582 21483 21506 I Unity   : UnityEngine.Purchasing.Extension.UnityUtil:Update()
    5. 12-02 13:15:21.582 21483 21506 I Unity   :
    6. 12-02 13:15:21.582 21483 21506 I Unity   : (Filename: ./Runtime/Export/Debug/Debug.bindings.h Line: 35)
    I am not seeing a ProcessPurchase call hit my code. Still no receipt in controller.products.all.
     
  46. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Does the user show as having purchased the product in their Google account? Also, and only as a test, can you bypass your server validation and directly return Complete from ProcessPurchase? This will help to troubleshoot.
     
  47. TranquilBits

    TranquilBits

    Joined:
    Feb 1, 2018
    Posts:
    15
    Yes. From admin view, Order Management on the Play Console the transaction shows as charged. In the user account, I got a email confirmation of the charge. It was a "Test Card". Would this make a difference?

    I will repeat the test as requested.
     
  48. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Thanks, it sounds like a bug, thanks for your help. A test purchase should not make a difference.
     
  49. TranquilBits

    TranquilBits

    Joined:
    Feb 1, 2018
    Posts:
    15
    First, purchasing the item where ProcessPurchase immediately returns Complete:

    Code (CSharp):
    1. 12-02 14:01:34.776 29413 29436 I Unity   : Processing Purchase for item: id_3x_coins_google. Receipt: {"Store":"GooglePlay","TransactionID":"ekdlpbgjmaicihjmlmpdldjc.AO-J1OwEECSCHxUdX5OKTk98JunIKxRskgynXF5RQ1m1zT43kQS_kfeJVq7y5wNvDwnw8cZC9_tn4Bc3L5kRk4IVF75pN_WOpVWF3HDI1T_WuRTRpMlR3mU","Payload":"{\"json\":\"{\\\"orderId\\\":\\\"GPA.3305-9654-9598-95332\\\",\\\"packageName\\\":\\\"com.tranquilbits.rumblerocket\\\",\\\"productId\\\":\\\"id_3x_coins_google\\\",\\\"purchaseTime\\\":1606935689267,\\\"purchaseState\\\":0,\\\"purchaseToken\\\":\\\"ekdlpbgjmaicihjmlmpdldjc.AO-J1OwEECSCHxUdX5OKTk98JunIKxRskgynXF5RQ1m1zT43kQS_kfeJVq7y5wNvDwnw8cZC9_tn4Bc3L5kRk4IVF75pN_WOpVWF3HDI1T_WuRTRpMlR3mU\\\",\\\"acknowledged\\\":false}\",\"signature\":\"g85H87UXkzllvZ6Ki021i5Uo5UVdWXu5xK4UAzJ5Wbw0fK1WR6w8v/3gDTka6HFfegsmrq63SVyYWvXmRypKIatBVbB2tDFeEMNDbOxOqsITXR0t3nTPNTfqj/zwbs1aiAaYZ3tw3AJtgVV30utiIgoYHC/E4VXBtR4py5oOVfvz+0I1CESKpTPbtVXaY8E8215v6SK9nE0Bvb5qYRXVvpvGX9W5NUJ2NOpyYoW/706JQr3UIQt66TTAjUDuIF4eBtmV8gCcBSSvNvlEnuz/JQFduYL5lTKRXE8Y9j36LcUMWFunzYojvKUgGmhSM1TeaDa
    2. 12-02 14:01:34.777 29413 29436 I Unity   : un0LwUR725QWeaM2jQ==\",\"skuDetails\":\"{\\\"productId\\\":\\\"id_3x_coins_google\\\",\\\"type\\\":\\\"inapp\\\",\\\"price\\\":\\\"$2.49\\\",\\\"price_amount_micros\\\":2490000,\\\"price_currency_code\\\":\\\"USD\\\",\\\"title\\\":\\\"Coin Bonus (Rumble Rocket)\\\",\\\"description\\\":\\\"Get 3x coins for earned coin rewards.\\\",\\\"skuDetailsToken\\\":\\\"AEuhp4IDU2EXoTif3UQLPMtDoRxbrgEhCBZybGLhlDm8b5zWSWghBgp95apVB0swd8w=\\\"}\"}"}
    3. 12-02 14:01:34.777 29413 29436 I Unity   : Purchaser:ProcessPurchase(PurchaseEventArgs)
    4. 12-02 14:01:34.777 29413 29436 I Unity   : UnityEngine.Purchasing.PurchasingManager:ProcessPurchaseIfNew(Product)
    5. 12-02 14:01:34.777 29413 29436 I Unity   : System.Action:Invoke()
    6. 12-02 14:01:34.777 29413 29436 I Unity   : UnityEngine.Purchasing.Extension.UnityUtil:Update()
    7. 12-02 14:01:34.777 29413 29436 I Unity   :
    8. 12-02 14:01:34.777 29413 29436 I Unity   : (Filename: ./Runtime/Export/Debug/Debug.bindings.h Line: 35)
    9. 12-02 14:01:34.777 29413 29436 I Unity   :
    10. 12-02 14:01:35.518 29413 29436 I Unity   : Already recorded transaction ekdlpbgjmaicihjmlmpdldjc.AO-J1OwEECSCHxUdX5OKTk98JunIKxRskgynXF5RQ1m1zT43kQS_kfeJVq7y5wNvDwnw8cZC9_tn4Bc3L5kRk4IVF75pN_WOpVWF3HDI1T_WuRTRpMlR3mU
    11. 12-02 14:01:35.518 29413 29436 I Unity   : UnityEngine.Purchasing.PurchasingManager:ProcessPurchaseIfNew(Product)
    12. 12-02 14:01:35.518 29413 29436 I Unity   : System.Action:Invoke()
    13. 12-02 14:01:35.518 29413 29436 I Unity   : UnityEngine.Purchasing.Extension.UnityUtil:Update()
    14. 12-02 14:01:35.518 29413 29436 I Unity   :
    15. 12-02 14:01:35.518 29413 29436 I Unity   : (Filename: ./Runtime/Export/Debug/Debug.bindings.h Line: 35)
    16. 12-02 14:01:35.518 29413 29436 I Unity   :
    Then, close and relaunch the game:

    Code (CSharp):
    1. 12-02 14:02:48.311 29758 29788 I Unity   : UnityIAP Version: 2.2.2
    2. 12-02 14:02:48.311 29758 29788 I Unity   : UnityEngine.Purchasing.StandardPurchasingModule:Instance(AppStore)
    3. 12-02 14:02:48.311 29758 29788 I Unity   : Purchaser:initialize()
    4. 12-02 14:02:48.311 29758 29788 I Unity   : Purchaser:onCloudLogin(Boolean)
    5. 12-02 14:02:48.311 29758 29788 I Unity   : CloudLoginComplete:Invoke(Boolean)
    6. 12-02 14:02:48.311 29758 29788 I Unity   : CloudManager:OnSuccess_Authenticate(String, Object)
    7. 12-02 14:02:48.311 29758 29788 I Unity   : BrainCloud.SuccessCallback:Invoke(String, Object)
    8. 12-02 14:02:48.311 29758 29788 I Unity   : BrainCloud.SuccessCallback:Invoke(String, Object)
    9. 12-02 14:02:48.311 29758 29788 I Unity   : BrainCloud.Internal.BrainCloudComms:HandleResponseBundle(String)
    10. 12-02 14:02:48.311 29758 29788 I Unity   : BrainCloud.Internal.BrainCloudComms:Update()
    11. 12-02 14:02:48.311 29758 29788 I Unity   : BrainCloud.BrainCloudClient:Update(eBrainCloudUpdateType)
    12. 12-02 14:02:48.311 29758 29788 I Unity   :
    13. 12-02 14:02:48.311 29758 29788 I Unity   : (Filename: ./Runtime/Export/Debug/Debug.bindings.h Line: 35)
    14. 12-02 14:02:48.311 29758 29788 I Unity   :
    15. 12-02 14:02:48.436 29758 29788 I Unity   : Iterating over products in OnInitialized...
    16. 12-02 14:02:48.436 29758 29788 I Unity   : Purchaser:OnInitialized(IStoreController, IExtensionProvider)
    17. 12-02 14:02:48.436 29758 29788 I Unity   : UnityEngine.Purchasing.PurchasingManager:CheckForInitialization()
    18. 12-02 14:02:48.436 29758 29788 I Unity   : UnityEngine.Purchasing.PurchasingManager:OnProductsRetrieved(List`1)
    19. 12-02 14:02:48.436 29758 29788 I Unity   : System.Action:Invoke()
    20. 12-02 14:02:48.436 29758 29788 I Unity   : UnityEngine.Purchasing.Extension.UnityUtil:Update()
    21. 12-02 14:02:48.436 29758 29788 I Unity   :
    22. 12-02 14:02:48.436 29758 29788 I Unity   : (Filename: ./Runtime/Export/Debug/Debug.bindings.h Line: 35)
    23. 12-02 14:02:48.436 29758 29788 I Unity   :
    24. 12-02 14:02:48.437 29758 29788 I Unity   : Coin Bonus (Rumble Rocket) - Get 3x coins for earned coin rewards. - USD - 2.49 - $2.49 -  -
    25. 12-02 14:02:48.437 29758 29788 I Unity   : Purchaser:OnInitialized(IStoreController, IExtensionProvider)
    26. 12-02 14:02:48.437 29758 29788 I Unity   : UnityEngine.Purchasing.PurchasingManager:CheckForInitialization()
    27. 12-02 14:02:48.437 29758 29788 I Unity   : UnityEngine.Purchasing.PurchasingManager:OnProductsRetrieved(List`1)
    28. 12-02 14:02:48.437 29758 29788 I Unity   : System.Action:Invoke()
    29. 12-02 14:02:48.437 29758 29788 I Unity   : UnityEngine.Purchasing.Extension.UnityUtil:Update()
    30. 12-02 14:02:48.437 29758 29788 I Unity   :
    31. 12-02 14:02:48.437 29758 29788 I Unity   : (Filename: ./Runtime/Export/Debug/Debug.bindings.h Line: 35)
    32. 12-02 14:02:48.437 29758 29788 I Unity   :
    33. 12-02 14:02:48.437 29758 29788 I Unity   : 1000 Coin Pack (Rumble Rocket) - 1000 coins to use in the in game store. - USD - 0.99 - $0.99 -  -
    34. 12-02 14:02:48.437 29758 29788 I Unity   : Purchaser:OnInitialized(IStoreController, IExtensionProvider)
    35. 12-02 14:02:48.437 29758 29788 I Unity   : UnityEngine.Purchasing.PurchasingManager:CheckForInitialization()
    36. 12-02 14:02:48.437 29758 29788 I Unity   : UnityEngine.Purchasing.PurchasingManager:OnProductsRetrieved(List`1)
    37. 12-02 14:02:48.437 29758 29788 I Unity   : System.Action:Invoke()
    38. 12-02 14:02:48.437 29758 29788 I Unity   : UnityEngine.Purchasing.Extension.UnityUtil:Update()
    39. 12-02 14:02:48.437 29758 29788 I Unity   :
    40. 12-02 14:02:48.437 29758 29788 I Unity   : (Filename: ./Runtime/Export/Debug/Debug.bindings.h Line: 35)
    41. 12-02 14:02:48.437 29758 29788 I Unity   :
    42. 12-02 14:02:48.437 29758 29788 I Unity   : 3300 Coin Pack (Rumble Rocket) - 3300 Coins (300 free) to use in the in game store. - USD - 2.99 - $2.99 -  -
    43. 12-02 14:02:48.437 29758 29788 I Unity   : Purchaser:OnInitialized(IStoreController, IExtensionProvider)
    44. 12-02 14:02:48.437 29758 29788 I Unity   : UnityEngine.Purchasing.PurchasingManager:CheckForInitialization()
    45. 12-02 14:02:48.437 29758 29788 I Unity   : UnityEngine.Purchasing.PurchasingManager:OnProductsRetrieved(List`1)
    46. 12-02 14:02:48.437 29758 29788 I Unity   : System.Action:Invoke()
    47. 12-02 14:02:48.437 29758 29788 I Unity   : UnityEngine.Purchasing.Extension.UnityUtil:Update()
    48. 12-02 14:02:48.437 29758 29788 I Unity   :
    49. 12-02 14:02:48.437 29758 29788 I Unity   : (Filename: ./Runtime/Export/Debug/Debug.bindings.h Line: 35)
    50. 12-02 14:02:48.437 29758 29788 I Unity   :
    51. 12-02 14:02:48.438 29758 29788 I Unity   : 6000 Coin Pack (Rumble Rocket) - 6000 Coins (1000 free) to use in the in game store. - USD - 4.99 - $4.99 -  -
    52. 12-02 14:02:48.438 29758 29788 I Unity   : Purchaser:OnInitialized(IStoreController, IExtensionProvider)
    53. 12-02 14:02:48.438 29758 29788 I Unity   : UnityEngine.Purchasing.PurchasingManager:CheckForInitialization()
    54. 12-02 14:02:48.438 29758 29788 I Unity   : UnityEngine.Purchasing.PurchasingManager:OnProductsRetrieved(List`1)
    55. 12-02 14:02:48.438 29758 29788 I Unity   : System.Action:Invoke()
    56. 12-02 14:02:48.438 29758 29788 I Unity   : UnityEngine.Purchasing.Extension.UnityUtil:Update()
    57. 12-02 14:02:48.438 29758 29788 I Unity   :
    58. 12-02 14:02:48.438 29758 29788 I Unity   : (Filename: ./Runtime/Export/Debug/Debug.bindings.h Line: 35)
    59. 12-02 14:02:48.438 29758 29788 I Unity   :
    60. 12-02 14:02:48.441 29758 29788 I Unity   : 13000 Coin Pack (Rumble Rocket) - 13000 Coins (3000 free) to use in the in game store. - USD - 9.99 - $9.99 -  -
    61. 12-02 14:02:48.441 29758 29788 I Unity   : Purchaser:OnInitialized(IStoreController, IExtensionProvider)
    62. 12-02 14:02:48.441 29758 29788 I Unity   : UnityEngine.Purchasing.PurchasingManager:CheckForInitialization()
    63. 12-02 14:02:48.441 29758 29788 I Unity   : UnityEngine.Purchasing.PurchasingManager:OnProductsRetrieved(List`1)
    64. 12-02 14:02:48.441 29758 29788 I Unity   : System.Action:Invoke()
    65. 12-02 14:02:48.441 29758 29788 I Unity   : UnityEngine.Purchasing.Extension.UnityUtil:Update()
    66. 12-02 14:02:48.441 29758 29788 I Unity   :
    67. 12-02 14:02:48.441 29758 29788 I Unity   : (Filename: ./Runtime/Export/Debug/Debug.bindings.h Line: 35)
    68. 12-02 14:02:48.441 29758 29788 I Unity   :
    69. 12-02 14:02:48.470 29758 29788 I Unity   : Already recorded transaction ekdlpbgjmaicihjmlmpdldjc.AO-J1OwEECSCHxUdX5OKTk98JunIKxRskgynXF5RQ1m1zT43kQS_kfeJVq7y5wNvDwnw8cZC9_tn4Bc3L5kRk4IVF75pN_WOpVWF3HDI1T_WuRTRpMlR3mU
    70. 12-02 14:02:48.470 29758 29788 I Unity   : UnityEngine.Purchasing.PurchasingManager:ProcessPurchaseIfNew(Product)
    71. 12-02 14:02:48.470 29758 29788 I Unity   : System.Action:Invoke()
    72. 12-02 14:02:48.470 29758 29788 I Unity   : UnityEngine.Purchasing.Extension.UnityUtil:Update()
    73. 12-02 14:02:48.470 29758 29788 I Unity   :
    74. 12-02 14:02:48.470 29758 29788 I Unity   : (Filename: ./Runtime/Export/Debug/Debug.bindings.h Line: 35)
    The format of the Product log is as DustyDev posted earlier:
    Code (CSharp):
    1. Debug.Log(string.Join(" - ",
    2.                     new[]
    3.                     {
    4.                         item.metadata.localizedTitle,
    5.                         item.metadata.localizedDescription,
    6.                         item.metadata.isoCurrencyCode,
    7.                         item.metadata.localizedPrice.ToString(),
    8.                         item.metadata.localizedPriceString,
    9.                         item.transactionID,
    10.                         item.receipt
    11.                     }));
    You can observe the lack of receipts.
     
  50. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    @TranquilBits It looks like you may be initializing IAP multiple times? I'm seeing repeated OnInitialized, can you confirm? Does it work as expected with you directly return Complete, please describe what we are looking at in the logs. It appears it makes no difference whether your return Complete vs Pending + ConfirmPendingPurchase, correct?
     
Thread Status:
Not open for further replies.