Search Unity

Unity IAP How to Retry PurchaseProcessingResult.Pending Purchase?

Discussion in 'Unity IAP' started by TitanUnity, Jun 26, 2018.

  1. TitanUnity

    TitanUnity

    Joined:
    May 15, 2014
    Posts:
    180
    Hi,

    We're having trouble retrying Pending purchases since upgrading to Unity 2018.1.3f1. Our payment flow works like this:

    PurchaseProcessingResult.Pending.png


    The specific problem occurs if our App Server is unavailable for any reason. So in a test scenario let's say we disable our App server (preventing steps 6 and 7 from completing). You can see that the ConfirmPendingPurchase call will never fires so the purchase is left in a Pending state.

    Our assumption was that by retrying the purchase of the product we could "retrigger" the validation process and the purchase would be completed, but this doesn't work:
    m_StoreController.InitiatePurchase(product);

    It just fires an error: PurchaseFailureReason.DuplicateTransaction

    Was something changed? The only way we've found to make this work so far is to close the app and restart, forcing the store to re-initialize which will retrigger ProcessPurchase for the Pending transaction.
     
  2. TitanUnity

    TitanUnity

    Joined:
    May 15, 2014
    Posts:
    180
  3. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    I am checking the IAP team and will follow up
     
  4. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    If previously you attempted to purchase again and if it succeeded, wouldn't that orphan the first transaction and leave it in pending? Cannot you retry Step 6 above and implement a time out and/or error return with a retry mechanism, instead of attempting to purchase again? We suspect that the previous behavior was in error. What would be your ideal behavior for the IAP system (other than attempt to repurchase)?
     
  5. TitanUnity

    TitanUnity

    Joined:
    May 15, 2014
    Posts:
    180
    Before installing Unity 2018, if a purchase was left in a Pending state as described above, if we called InitiatePurchase
    Code (CSharp):
    1. m_StoreController.InitiatePurchase(product);
    the Unity IAP system would automatically retrigger ProcessPurchase:
    Code (CSharp):
    1. public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs e){ ..  }
    and the Pending purchase for the user would be automatically Completed and delivered.

    It seems now that when calling InitiatePurchase() for a product that is in a pending state, the Unity IAP system throws a PurchaseFailureReason.DuplicateTransaction error instead.

    As you mentioned, if the original behavior of the Unity IAP system automatically calling ProcessPurchase was an error that at least makes sense, but you're right, we now need to implement our own retry logic for player's that encounter purchase problems during their session and then manually call
    Code (CSharp):
    1. m_StoreController.ConfirmPendingPurchase(product);
    when the purchase has been validated.

    We actually started built a flow last night that simulates what the Unity IAP did before. But to answer your question about our ideal behavior, the only thing that makes sense to us in the case that our App Server is inaccessible is to present the user with an error message suggesting that the purchase was NOT delivered successfully and to please try again. At that point, the user has 2 options:

    1. Relaunch our app and open our store UI which will automatically complete the transaction on IAP init:
    Code (CSharp):
    1. UnityPurchasing.Initialize();
    2. Try again (tap the product they wanted to buy again). The system would call a smart InitiatePurchase() that would detect an incomplete Pending transaction for that product and proceed to ProcessPurchase, if no Pending purchases were outstanding it would create a new transaction so there would be no orphan transactions. << We just built this flow manually, it looks something like this:

    Code (CSharp):
    1.     public override void PurchaseProduct(string sku)
    2.     {
    3.         try
    4.         {
    5.             if (!IsInitialized())
    6.             {
    7.                 OnPurchaseFailed("Store not initialized.");
    8.             }
    9.             else
    10.             {
    11.                 Product product = m_StoreController.products.WithID(sku);
    12.                 if (product == null)
    13.                 {
    14.                     OnPurchaseFailed("Product not found.");
    15.                 }
    16.                 else if (IsProductPending(product))
    17.                 {
    18.                     StartCoroutine(ValidateTransaction(product));
    19.                 }
    20.                 else if (!product.availableToPurchase)
    21.                 {
    22.                     OnPurchaseFailed("Product not available for purchase.");
    23.                 }
    24.                 else
    25.                 {
    26.                     m_StoreController.InitiatePurchase(product);
    27.                 }
    28.             }
    29.         }
    30.         catch (Exception e)
    31.         {
    32.             OnPurchaseFailed("Could not initiate purchase transaction. Error: " + e.Message);
    33.         }
    34.     }
    So you can see we're manually checking if the product is Pending by calling IsProductPending(). We also implement our own ValidateTransaction() coroutine that calls m_StoreController.ConfirmPendingPurchase(product) on success.
     
    Last edited: Jun 27, 2018
  6. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Attempting to purchase again is not valid flow. As mentioned, it would leave the previous transaction in an orphaned state. A DuplicatePurchase error the second time would be expected. Apologies if you saw different behavior in a previous version, the current flow is now correct as far as we can tell. During the Step 6 above, you would want to retry as you agreed. You can test Pending behavior with this sample project. It might make sense to create a separate button for Initialize, instead of in the Start() method. The Toggle button allows you to either return Pending or Complete from ProcessPurchase. Currently, if you have a product left in Pending, the next time the app starts and IAP initializes, ProcessPurchase (for the original purchase left in Pending) is triggered, and you can retry your validation again. Otherwise, if your server is down, you will need to retry at run time. Otherwise, you have two options. Simply pass the product to ConfirmPendingPurchase without the server validation, or relaunch the game.

    https://forum.unity.com/threads/sample-iap-project.529555/
     
    MihaPro_CarX likes this.
  7. stokato

    stokato

    Joined:
    Aug 20, 2018
    Posts:
    1
    But how can I check what the product is pending?
     
  8. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    @stokato Do you mean at app launch? If so, are you receiving a ProcessPurchase callback for the product? I've been testing with this sample app which allows you to configure Pending vs Complete at run time (using the Toggle button). I also write debug information to the UI. https://forum.unity.com/threads/sample-iap-project.529555/
     
  9. Lesha-VH

    Lesha-VH

    Joined:
    Jul 3, 2012
    Posts:
    96
    Unity 2017.4.8f1
    IAP 1.21.0
    Android/Google Play store

    I make purchase (press buy button after enter password), than - BEFORE google answer - disconnect device from internet.

    After restore internet connection and press the same item I get error in
    public void OnPurchaseFailed(Product i, PurchaseFailureReason p)
    with reason UserCancelled

    After re-launch application IAP plugin does not call
    public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs e)
    and I can not buy the same consumable item with reason UserCancelled

    I think purchases hangs somewhere - or I miss something?
    Thanks!
     
    Last edited: Sep 5, 2018
  10. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Understood, thank you for the description. Are you able to purchase after you reinstall the app?
     
  11. Lesha-VH

    Lesha-VH

    Joined:
    Jul 3, 2012
    Posts:
    96
    Jeff, after re-install I CAN NOT buy the same consumable product. (no call ProcessPurchase at app launch, when try to buy - OnPurchaseFailed with reason UserCancelled)
     
  12. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Got it, we are investigating.
     
  13. Lesha-VH

    Lesha-VH

    Joined:
    Jul 3, 2012
    Posts:
    96
    Jeff, did you reproduce the bug?
    Only install NEW application version from google play version fix it))
     
  14. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    @Lesha-VH Can you elaborate? Did you fix it?
     
  15. ThiagoUken

    ThiagoUken

    Joined:
    Sep 26, 2018
    Posts:
    1
    I'm having the same issue with pending purchases. Is there a more elegant way to handle it without having to restart the app? I tried calling UnityPurchasing.Initialize again without restarting but it doesn't fire the ProcessPurchase event.
     
  16. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
  17. SyedUmair

    SyedUmair

    Joined:
    Sep 2, 2013
    Posts:
    43
    I am also having exact problem. Unity Team should handle it in a better way or at least provide a method to fetch pending purchases at run-time. So, we can check and call ConfirmPendingPurchase without the need to re-launch the app.
     
  18. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Your pending purchases will be available when you initialize IAP. You can initialize IAP at any time. But yes, we are working on an improved solution.
     
  19. SyedUmair

    SyedUmair

    Joined:
    Sep 2, 2013
    Posts:
    43
    it's great, if you guys are working to improve the flow.
    Yes, for now, I would do multiple initializes to get the pending transactions. But, I guess, its not the best idea to call Init methods once SDK is initialized, successfully. :)
     
  20. jjurica

    jjurica

    Joined:
    Feb 14, 2017
    Posts:
    1
    Hello. I was wondering if you would kindly share IsProductPending() and ValidateTransaction() coroutine? There still seems to be missing information on how to access the list of pending purchases. Thanks
     
  21. Alexander_DEV

    Alexander_DEV

    Joined:
    Jun 22, 2015
    Posts:
    1
    @TitanUnity
    Hey. I ran into the same problem that you described.
    Can you tell how you determine that the purchase is still pending after the application is restarted? Can you show the "IsProductPending" method code?
     
  22. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    There is no specific product pending method. Your ProcessPurchase will automatically fire upon IAP initialization if you have any products in Pending, it's the same method that fires when someone attempts to purchase the product in your game. You can confirm with the Sample IAP Project here, it allows you to toggle between Pending and Complete during purchase for testing. If you choose Pending, the next time you launch the game, you'll see the ProcessPurchase debug messages in the UI https://forum.unity.com/threads/sample-iap-project.529555/
     
  23. Ritwik_Sinha

    Ritwik_Sinha

    Joined:
    Apr 30, 2018
    Posts:
    3
    07-31 09:18:17.815: I/Unity(26850): UnityIAP Version: 1.22.0
    07-31 09:18:17.815: I/Unity(26850):
    07-31 09:18:17.815: I/Unity(26850): (Filename: ./Runtime/Export/Debug.bindings.h Line: 45)
    07-31 09:18:17.884: I/UnityIAP(26850): IAB helper created.
    07-31 09:18:17.950: W/UnityMain(26850): type=1400 audit(0.0:1833): avc: granted { create } for name="IAP" scontext=u:r:untrusted_app:s0:c12,c258,c512,c768 tcontext=u:eek:bject_r:sdcardfs:s0:c12,c258,c512,c768 tclass=dir
    07-31 09:18:18.506: I/Unity(26850): Using configuration builder objects
    07-31 09:18:18.506: I/Unity(26850):
    07-31 09:18:18.506: I/Unity(26850): (Filename: ./Runtime/Export/Debug.bindings.h Line: 45)
    07-31 09:18:18.540: I/UnityIAP(26850): Starting in-app billing setup.
    07-31 09:18:18.545: I/UnityIAP(26850): Billing service connected.
    07-31 09:18:18.546: I/UnityIAP(26850): invoking callback
    07-31 09:18:18.546: I/UnityIAP(26850): Checking for in-app billing 3 support.
    07-31 09:18:18.553: I/UnityIAP(26850): In-app billing version 3 supported for com.Idivarts.SpaceKill
    07-31 09:18:18.557: I/UnityIAP(26850): Subscriptions AVAILABLE.
    07-31 09:18:18.559: I/UnityIAP(26850): Subscription upgrade and downgrade are AVAILABLE.
    07-31 09:18:18.562: I/UnityIAP(26850): Subscriptions information parse AVAILABLE.
    07-31 09:18:18.576: I/UnityIAP(26850): VR supported.
    07-31 09:18:18.577: I/UnityIAP(26850): onIabSetupFinished: 0
    07-31 09:18:18.577: I/UnityIAP(26850): Requesting 11 products
    07-31 09:18:18.577: I/UnityIAP(26850): QueryInventory: 11
    07-31 09:18:18.578: I/UnityIAP(26850): invoking callback
    07-31 09:18:18.578: I/UnityIAP(26850): Querying owned items, item type: inapp
    07-31 09:18:18.578: I/UnityIAP(26850): Package name: com.Idivarts.SpaceKill
    07-31 09:18:18.578: I/UnityIAP(26850): Calling getPurchases with continuation token: null
    07-31 09:18:18.582: I/UnityIAP(26850): Owned items response: 0
    07-31 09:18:18.582: I/UnityIAP(26850): Continuation token: null
    07-31 09:18:18.582: I/UnityIAP(26850): Querying SKU details.
    07-31 09:18:18.623: I/UnityIAP(26850): Querying owned items, item type: subs
    07-31 09:18:18.623: I/UnityIAP(26850): Package name: com.Idivarts.SpaceKill
    07-31 09:18:18.623: I/UnityIAP(26850): Calling getPurchases with continuation token: null
    07-31 09:18:18.623: I/UnityIAP(26850): [ 07-31 09:18:18.657 26850:27252 D/ ]
    07-31 09:18:18.623: I/UnityIAP(26850): addLayerName, tid:27252
    07-31 09:18:18.833: I/UnityIAP(26850): Owned items response: 0
    07-31 09:18:18.833: I/UnityIAP(26850): Continuation token: null
    07-31 09:18:18.834: I/UnityIAP(26850): Querying SKU details.
    07-31 09:18:18.847: I/UnityIAP(26850): Querying owned items' purchase history, item type: subs
    07-31 09:18:18.847: I/UnityIAP(26850): Package name: com.Idivarts.SpaceKill
    07-31 09:18:18.847: I/UnityIAP(26850): Calling getPurchaseHistory with continuation token: null
    07-31 09:18:19.355: I/UnityIAP(26850): Purchase history response: 0
    07-31 09:18:19.355: I/UnityIAP(26850): Continuation token: null
    07-31 09:18:19.355: I/UnityIAP(26850): Querying owned items' purchase history, item type: inapp
    07-31 09:18:19.355: I/UnityIAP(26850): Package name: com.Idivarts.SpaceKill
    07-31 09:18:19.355: I/UnityIAP(26850): Calling getPurchaseHistory with continuation token: null
    07-31 09:18:19.627: I/UnityIAP(26850): Purchase history response: 0
    07-31 09:18:19.628: I/UnityIAP(26850): Continuation token: null
    07-31 09:18:19.628: I/UnityIAP(26850): onQueryInventoryFinished: true
    07-31 09:18:19.628: I/UnityIAP(26850): Inventory refresh successful. (response: 0:OK)
    07-31 09:18:19.637: I/UnityIAP(26850): invoking callback
    07-31 09:18:19.725: I/Unity(26850): UnityIAP: Promo interface is available for 11 items
    07-31 09:18:19.725: I/Unity(26850):
    07-31 09:18:19.725: I/Unity(26850): (Filename: ./Runtime/Export/Debug.bindings.h Line: 45)
    07-31 09:18:19.725: I/Unity(26850): [ 07-31 09:18:19.741 26850:27252 D/ ]
    07-31 09:18:19.725: I/Unity(26850): addLayerName, tid:27252
    07-31 09:18:19.725: I/Unity(26850): [ 07-31 09:18:19.782 26850:27252 D/ ]
    07-31 09:18:19.725: I/Unity(26850): addLayerName, tid:27252
    07-31 09:18:20.215: I/UnityIAP(26850): invoking callback
    07-31 09:18:20.717: I/UnityIAP(26850): invoking callback
    07-31 09:18:21.269: I/UnityIAP(26850): invoking callback
    07-31 09:18:21.829: I/UnityIAP(26850): invoking callback
    07-31 09:18:22.381: I/UnityIAP(26850): invoking callback
    07-31 09:18:22.913: I/UnityIAP(26850): invoking callback
    07-31 09:18:23.461: I/UnityIAP(26850): invoking callback
    07-31 09:18:23.966: I/UnityIAP(26850): invoking callback
    07-31 09:18:24.481: I/UnityIAP(26850): invoking callback
    07-31 09:18:37.243: I/Unity(26850): Tag Name : OtherUI
    07-31 09:18:37.243: I/Unity(26850):
    07-31 09:18:37.243: I/Unity(26850): (Filename: ./Runtime/Export/Debug.bindings.h Line: 45)
    07-31 09:18:37.243: I/Unity(26850): [ 07-31 09:18:37.288 26850:27252 D/ ]
    07-31 09:18:37.243: I/Unity(26850): addLayerName, tid:27252
    07-31 09:18:38.943: I/Unity(26850): Tag Name : OtherUI
    07-31 09:18:38.943: I/Unity(26850):
    07-31 09:18:38.943: I/Unity(26850): (Filename: ./Runtime/Export/Debug.bindings.h Line: 45)
    07-31 09:18:39.248: I/Unity(26850): Receipt ;
    07-31 09:18:39.248: I/Unity(26850):
    07-31 09:18:39.248: I/Unity(26850): (Filename: ./Runtime/Export/Debug.bindings.h Line: 45)
    07-31 09:18:39.248: I/Unity(26850): Transaction Id :
    07-31 09:18:39.248: I/Unity(26850):
    07-31 09:18:39.248: I/Unity(26850): (Filename: ./Runtime/Export/Debug.bindings.h Line: 45)
    07-31 09:18:39.248: I/Unity(26850): Meta data : unlockAllLevels (Space Kill)
    07-31 09:18:39.248: I/Unity(26850):
    07-31 09:18:39.248: I/Unity(26850): (Filename: ./Runtime/Export/Debug.bindings.h Line: 45)
    07-31 09:18:39.248: I/Unity(26850): Meta data : unlockAllLevels
    07-31 09:18:39.248: I/Unity(26850):
    07-31 09:18:39.248: I/Unity(26850): (Filename: ./Runtime/Export/Debug.bindings.h Line: 45)
    07-31 09:18:39.248: I/Unity(26850): Meta data : ? 450.00
    07-31 09:18:39.248: I/Unity(26850):
    07-31 09:18:39.248: I/Unity(26850): (Filename: ./Runtime/Export/Debug.bindings.h Line: 45)
    07-31 09:18:39.249: I/Unity(26850): Availability : True
    07-31 09:18:39.249: I/Unity(26850):
    07-31 09:18:39.249: I/Unity(26850): (Filename: ./Runtime/Export/Debug.bindings.h Line: 45)
    07-31 09:18:39.249: I/Unity(26850): Has Receipt : False
    07-31 09:18:39.249: I/Unity(26850):
    07-31 09:18:39.249: I/Unity(26850): (Filename: ./Runtime/Export/Debug.bindings.h Line: 45)
    07-31 09:18:39.249: I/Unity(26850): Definition store Id : offer4
    07-31 09:18:39.249: I/Unity(26850):
    07-31 09:18:39.249: I/Unity(26850): (Filename: ./Runtime/Export/Debug.bindings.h Line: 45)
    07-31 09:18:39.249: I/Unity(26850): Purchasing product asychronously: offer4
    07-31 09:18:39.249: I/Unity(26850):
    07-31 09:18:39.249: I/Unity(26850): (Filename: ./Runtime/Export/Debug.bindings.h Line: 45)
    07-31 09:18:39.251: I/Unity(26850): Unable to confirm purchase; Product has missing or empty transactionID
    07-31 09:18:39.251: I/Unity(26850):
    07-31 09:18:39.251: I/Unity(26850): (Filename: ./Runtime/Export/Debug.bindings.h Line: 45)
    07-31 09:18:39.499: I/Unity(26850): Receipt ;
    07-31 09:18:39.499: I/Unity(26850):
    07-31 09:18:39.499: I/Unity(26850): (Filename: ./Runtime/Export/Debug.bindings.h Line: 45)
    07-31 09:18:39.499: I/Unity(26850): Transaction Id :
    07-31 09:18:39.499: I/Unity(26850):
    07-31 09:18:39.499: I/Unity(26850): (Filename: ./Runtime/Export/Debug.bindings.h Line: 45)
    07-31 09:18:39.499: I/Unity(26850): Meta data : unlockAllLevels (Space Kill)
    07-31 09:18:39.499: I/Unity(26850):
    07-31 09:18:39.499: I/Unity(26850): (Filename: ./Runtime/Export/Debug.bindings.h Line: 45)
    07-31 09:18:39.499: I/Unity(26850): Meta data : unlockAllLevels
    07-31 09:18:39.499: I/Unity(26850):
    07-31 09:18:39.499: I/Unity(26850): (Filename: ./Runtime/Export/Debug.bindings.h Line: 45)
    07-31 09:18:39.499: I/Unity(26850): Meta data : ? 450.00
    07-31 09:18:39.499: I/Unity(26850):
    07-31 09:18:39.499: I/Unity(26850): (Filename: ./Runtime/Export/Debug.bindings.h Line: 45)
    07-31 09:18:39.499: I/Unity(26850): Availability : True
    07-31 09:18:39.499: I/Unity(26850):
    07-31 09:18:39.499: I/Unity(26850): (Filename: ./Runtime/Export/Debug.bindings.h Line: 45)
    07-31 09:18:39.500: I/Unity(26850): Has Receipt : False
    07-31 09:18:39.500: I/Unity(26850):
    07-31 09:18:39.500: I/Unity(26850): (Filename: ./Runtime/Export/Debug.bindings.h Line: 45)
    07-31 09:18:39.500: I/Unity(26850): Definition store Id : offer4
    07-31 09:18:39.500: I/Unity(26850):
    07-31 09:18:39.500: I/Unity(26850): (Filename: ./Runtime/Export/Debug.bindings.h Line: 45)
    07-31 09:18:39.500: I/Unity(26850): Purchasing product asychronously: offer4
    07-31 09:18:39.500: I/Unity(26850):
    07-31 09:18:39.500: I/Unity(26850): (Filename: ./Runtime/Export/Debug.bindings.h Line: 45)
    07-31 09:18:39.500: I/Unity(26850): Unable to confirm purchase; Product has missing or empty transactionID
    07-31 09:18:39.500: I/Unity(26850):
    07-31 09:18:39.500: I/Unity(26850): (Filename: ./Runtime/Export/Debug.bindings.h Line: 45)
    07-31 09:18:40.227: I/UnityIAP(26850): isUnityVrEnabled = false
    07-31 09:18:40.229: I/UnityIAP(26850): onPurchaseProduct: offer4
    07-31 09:18:40.229: I/UnityIAP(26850): ITEM TYPE:inapp
    07-31 09:18:40.264: I/Unity(26850): purchase({0}): offer4
    07-31 09:18:40.264: I/Unity(26850):
    07-31 09:18:40.264: I/Unity(26850): (Filename: ./Runtime/Export/Debug.bindings.h Line: 45)
    07-31 09:18:40.265: I/UnityIAP(26850): isUnityVrEnabled = false
    07-31 09:18:40.267: I/Unity(26850): purchase({0}): offer4
    07-31 09:18:40.267: I/Unity(26850):
    07-31 09:18:40.267: I/Unity(26850): (Filename: ./Runtime/Export/Debug.bindings.h Line: 45)
    07-31 09:18:40.312: I/UnityIAP(26850): Creating purchase activity
    07-31 09:18:40.312: I/UnityIAP(26850): oldSkuMetadata is null
    07-31 09:18:40.313: I/UnityIAP(26850): invoking callback
    07-31 09:18:40.313: I/UnityIAP(26850): Constructing buy intent for offer4, item type: inapp
    07-31 09:18:40.348: I/UnityIAP(26850): Launching buy intent for offer4. Request code: 999
    07-31 09:19:52.592: I/UnityIAP(26850): onActivityResult
    07-31 09:19:52.593: I/UnityIAP(26850): Purchase data: {"orderId":"GPA.3306-2252-6230-58270","packageName":"com.Idivarts.SpaceKill","productId":"offer4","purchaseTime":1564544989039,"purchaseState":0,"developerPayload":"{\"developerPayload\":\"\",\"is_free_trial\":false,\"has_introductory_price_trial\":false,\"is_updated\":false,\"accountId\":\"\"}","purchaseToken":(It displays some valid token here on logcat)}
    07-31 09:19:52.593: I/UnityIAP(26850): Data signature: (It returns some valid signature here)
    07-31 09:19:52.593: I/UnityIAP(26850): Successful resultcode from purchase activity.
    07-31 09:19:52.593: I/UnityIAP(26850): Purchase data: {"orderId":"GPA.3306-2252-6230-58270","packageName":"com.Idivarts.SpaceKill","productId":"offer4","purchaseTime":1564544989039,"purchaseState":0,"developerPayload":"{\"developerPayload\":\"\",\"is_free_trial\":false,\"has_introductory_price_trial\":false,\"is_updated\":false,\"accountId\":\"\"}","purchaseToken":(It displays some valid token here on logcat)}
    07-31 09:19:52.593: I/UnityIAP(26850): Data signature: (It returns some valid signature here)
    07-31 09:19:52.593: I/UnityIAP(26850): Extras: Bundle[{INAPP_PURCHASE_DATA={"orderId":"GPA.3306-2252-6230-58270","packageName":"com.Idivarts.SpaceKill","productId":"offer4","purchaseTime":1564544989039,"purchaseState":0,"developerPayload":"{\"developerPayload\":\"\",\"is_free_trial\":false,\"has_introductory_price_trial\":false,\"is_updated\":false,\"accountId\":\"\"}","purchaseToken":(It displays some valid token here on logcat), RESPONSE_CODE=0}]
    07-31 09:19:52.593: I/UnityIAP(26850): Expected item type: inapp
    07-31 09:19:52.593: I/UnityIAP(26850): [ 07-31 09:19:52.595 1436: 1445 E/ ]
    07-31 09:19:52.593: I/UnityIAP(26850): [ZeroHung]zrhung_get_config: Get config failed for wp[0x0008]
    07-31 09:19:52.595: I/UnityIAP(26850): onIabPurchaseFinished: true
    07-31 09:19:52.595: I/UnityIAP(26850): Success (response: 0:OK)
    07-31 09:19:52.595: I/UnityIAP(26850): Product purchased successfully!
    07-31 09:19:52.595: I/UnityIAP(26850): NotifyUnityOfPurchase
    07-31 09:19:52.854: I/Unity(26850): onPurchaseFailedEvent({0}): offer4
    07-31 09:19:52.854: I/Unity(26850):
    07-31 09:19:52.854: I/Unity(26850): (Filename: ./Runtime/Export/Debug.bindings.h Line: 45)
    07-31 09:19:52.869: I/Unity(26850): OnPurchaseFailed: FAIL. Product: 'offer4', PurchaseFailureReason: ExistingPurchasePending
    07-31 09:19:52.869: I/Unity(26850):
    07-31 09:19:52.869: I/Unity(26850): (Filename: ./Runtime/Export/Debug.bindings.h Line: 45)
    07-31 09:19:52.869: I/Unity(26850): Unable to confirm purchase; Product has missing or empty transactionID
    07-31 09:19:52.869: I/Unity(26850):
    07-31 09:19:52.869: I/Unity(26850): (Filename: ./Runtime/Export/Debug.bindings.h Line: 45)
    07-31 09:19:52.905: I/UnityIAP(26850): Finish transaction:GPA.3306-2252-6230-58270
    07-31 09:19:52.905: I/UnityIAP(26850): Consuming offer4
    07-31 09:19:52.905: I/UnityIAP(26850): invoking callback
    07-31 09:19:52.905: I/UnityIAP(26850): Consuming sku: offer4, token: (It displays some valid token here on logcat)
    07-31 09:19:52.906: I/Unity(26850): UnityIAP: Promo interface is available for 11 items
    07-31 09:19:52.906: I/Unity(26850):
    07-31 09:19:52.906: I/Unity(26850): (Filename: ./Runtime/Export/Debug.bindings.h Line: 45)
    07-31 09:19:53.985: I/UnityIAP(26850): Successfully consumed sku: offer4
    07-31 09:19:53.985: I/UnityIAP(26850): onConsumeFinished:true
    07-31 09:19:53.985: I/UnityIAP(26850): Successful consume of sku offer4 (response: 0:OK)
    07-31 09:19:53.985: I/UnityIAP(26850): 0
    07-31 09:19:53.985: I/UnityIAP(26850): [ 07-31 09:19:53.998 610: 610 D/ ]
    07-31 09:19:53.985: I/UnityIAP(26850): Jitter happen,total Jitter:90,FPS:24






    Before when i was working, I could make purchases. After some time, this stopped happening. I have queried some item infos and added that here as well. The output is from android device monitor.

    Please help out.ProcessPurchase is not getting called even after app restart.

    I even tried out calling ConfirmPendingPurchase here but nothing seems to work. The output above is obtained via the script that i have used below. I gave some delay before initialization of purchase as well.
    Have a look at the snippet.
    Code (CSharp):
    1. void BuyProductID(string productId)
    2. {
    3. if (!IsInitialized())
    4. return;
    5. Product product = m_StoreController.products.WithID(productId);
    6. Debug.Log("Receipt ; \n"+product.receipt);
    7. Debug.Log("Transaction Id : "+product.transactionID);
    8. Debug.Log("Meta data : "+product.metadata.localizedTitle);
    9. Debug.Log("Meta data : "+product.metadata.localizedDescription);
    10. Debug.Log("Meta data : "+product.metadata.localizedPriceString);
    11. Debug.Log("Availability : "+product.availableToPurchase);
    12. Debug.Log("Has Receipt : "+product.hasReceipt);
    13. Debug.Log("Definition store Id : "+product.definition.storeSpecificId);
    14.  
    15. if (product != null)
    16. {
    17. Debug.Log("Purchasing product asychronously: "+ product.definition.id);
    18. manualPurchaseStarted=true;
    19. m_StoreController.ConfirmPendingPurchase(product);
    20. StartCoroutine(WaitAndStart(product));
    21. }
    22. else
    23. Debug.Log("BuyProductID: FAIL. Not purchasing product, either is not found or is not available for purchase");
    24. }
    25.  
    26. public IEnumerator WaitAndStart(Product product)
    27. {
    28. yield return new WaitForSeconds(1);
    29. m_StoreController.InitiatePurchase(product);
    30. }
     
  24. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Please show the code for ProcessPurchase. Did you return Pending on the original purchase? Are you able to make other purchases? I might recommend that you compare your code to the Sample IAP project here, it properly receives ProcessPurchase for any product left in Pending, it was designed for this specific test in mind, with the Toggle button logic. Please publish and test this sample app, and compare to your results. https://forum.unity.com/threads/sample-iap-project.529555/
     
  25. Ritwik_Sinha

    Ritwik_Sinha

    Joined:
    Apr 30, 2018
    Posts:
    3
    Yeah it worked. I tried logcat . In my case first the OnPurchaseFailed was called and then Process Purchase as i had introduced ConfirmPendingPurchase command there and as is there in the mentioned PureRocketIAP Project as well(as u mentioned). So after the purchase was failed it was purchased again(bcuz of
    m_StoreController.ConfirmPendingPurchase(product) ). I figured out that some flag that i had introduced before was not set to true as i was unaware of the fact that the control goes to OnPurchaseFailed first.

    Yet i dont know why is this flow everytime i purchase i.e. First OnPuchaseFailed is called and then ProcessPurchase
     
  26. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    You can't purchase a second time without the Purchase fail without confirming the first/prior purchase. This is expected.
     
  27. alex_roboto

    alex_roboto

    Joined:
    Nov 11, 2019
    Posts:
    26
    How do you know there aren't any pending purchases on start up? Do you just wait a frame after OnInitialized and if ProcessPurchase hasn't been called, you know there are no pending purchases?
     
  28. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    You should be prepared to handle ProcessPurchase at any time in your code. I would not suggest to "wait a frame" in case there are network delays. When are you seeing the ProcessPurchase in your testing?
     
  29. novaVision

    novaVision

    Joined:
    Nov 9, 2014
    Posts:
    518
    I have read entire thread but still didn't get, how to deal with such scenario:
    1. user initiated purchase and made a payment
    2. ProcessPurchase I return "Suspend" because I need to verify consumable purchase on a server
    3. Something occurred with a client/server and purchase wasn't validated (bad connection, app closed, etc)
    How to get pending purchases so I loop over and try to make API request to confirm purchases? I tried to initiate this scenario and didn't get any callback from Unity on next app start.
     
  30. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    There is no Suspend return code. It's either Complete or Pending, please show your code.
     
  31. Tx

    Tx

    Joined:
    Jul 4, 2012
    Posts:
    108
    Is it okay to call multiple times IAP initialization just to ensure pending IAP are processed again and sent to a remote server to check, confirm and give virtual goods? There is no new method to make a "light" version of this workflow still, right?
     
  32. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    No, you only want to initialize IAP once in your game, or unexpected behavior may result.
     
    Tx likes this.
  33. Tx

    Tx

    Joined:
    Jul 4, 2012
    Posts:
    108
    Sorry then I need a bit of clarification.
    It's a bit of a problem then because a lot of users do not close the apps but just leave them hibernated and so the app is never really closed.
    If I can't destroy runtime IAP (right?) and can't initialize it again I can just intialize it as soon as the app starts and leave it alone.
    But:
    1. user buy IAP XYZ and for any reason it's marked as pending and not confirmed.
    2. he writes to staff and says he hasn't got what he bought
    3. Staff has to give IAP XYZ virtual goods (and we have no proof he did in ios for example)
    4. User close the app, reboots phone etc and then the IAP system by itself fires a processpurchased to our server and the system gives again the virtual goods. We had no track of the previous payment so we can't find a duplicate. We could just ignore the first payment from this user but it's very hacky.

    I'm sorry if the question is stupid but really I'm a bit confused about this 'automatic retry' stuff that is not very customizable. I think it works 99% of the times. But that 1% is a disgruntled customer.
    Thanks.
     
    fulgorr likes this.
  34. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    It's not "again" as you mention, it's the first time, the user is only charged once. If you are returning Pending, you definitely don't want to give them "virtual goods" until the transaction is completed. Your staff just instructs them to restart the game. Why are you returning Pending? Also, you can always keep a copy of the product information, and call ConfirmPendingPurchase at any time, without reinitializing IAP. The Sample IAP project does this.
     
  35. Tx

    Tx

    Joined:
    Jul 4, 2012
    Posts:
    108
    Yes in our workflow we return Pending. Then send info to our remote server for receipt validation. Then send back the status and call ConfirmPendingPurchase.
    We are doing all ok.
    Our user base is extremely unfamiliar with computers. I believe that many are unable to restart an app. I will try to explain to staff in detail how they should handle these relatively rare problems.
    Thanks for the help. :)
     
  36. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Ideally you want your app to handle this situation so users don't have to restart the app or contact you.
     
  37. Tx

    Tx

    Joined:
    Jul 4, 2012
    Posts:
    108
    Sure, I would like to solve all in the app and in the most efficient and correct way.
    But:
    1. I can't destroy Unity IAP runtime, I didn't see any reference in the documentation that this is possible. There is not a UN-initialize IAP function (right?). And I can't call initialize again since I would do multiple initialization that is a no-no as you told me earlier.
    2. There isn't a function to poll the IAP system to ask if there was a pending transaction, correct?
    3. I can't restart app for users since IIRC it's against store rules (and it's an hack)
    4. Seems I can't find documentation of all the methods of the IStoreController anymore in the documentation for 2018.4 and if I go to the package manager to browse documentation of IAP unity sends me to a simple guide but no list of interfaces or classes present in the package (and it sends me to the wrong version of unity). Please fix guys, the packages are a great idea but without documentation it's a mess.

    Is there someone that solved this issue?
     
  38. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Why do you have a Pending transaction to start with? If you return Pending from your server check, you shouldn't award the user the product. When do you check your server again, how do you handle Pending transactions? The API hasn't changed, so the older documentation still applies.
     
  39. Tx

    Tx

    Joined:
    Jul 4, 2012
    Posts:
    108
    Mhhh I tried before to describe our current flow. I try to write in more detail.

    On Client
    User buys IAP (no virtual good is given yet)
    In ProcessPurchase (called by IAP) the Client sends notification to server with receipt and returns Pending
    And client goes to a "We are waiting your payment please wait a moment" screen

    On Server
    We get receipt and save all in a database
    Periodically there is a process that checks all new receipt and checks them (both google and ios)
    When receipt is processed (both ok or fake or duplicate ecc) we send back results to client. If Ok virtual goods are given to the user in the server database.

    On Client
    We get back result, display UI as needed and call ConfirmPendingPurchase.

    Since client could close app or have timeout issues I assume that a payment that I didn't confirm with the ConfirmPendingPurchase may trigger in the future other ProcessPurchase function calls with the same receipt (and I'm perfectly fine for this - I would only love to do a check when I want and not only on IAP initialization that is app start).

    I checked this flow many times with the flowchart provided with the documentation ( https://docs.unity3d.com/uploads/Main/PurchaseProcessingResult.Pending.png )
    Please check also points 1 and 2 of the previous question.
    Thank you
    p.s. thanks for your time. I'm sorry if the discussion seems so long and unuseful but I think it will help other developers too to have a real clear example of what to do.
     
    Last edited: Jun 24, 2020
    fulgorr likes this.
  40. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Sorry I still don't follow, are you actually seeing this behavior or are you speculating? If the user closed the app, then ProcessPurchase will be called when they launch the app again. Can you elaborate "I assume that a payment that I didn't confirm with the ConfirmPendingPurchase may trigger in the future other ProcessPurchase function calls with the same receipt." That is correct and expected, the user is only charged once. If you don't call ConfirmPendingPurchase, ProcessPurchase will be called on next app launch. You either clear the transaction with ConfirmPendingPurchase, or wait until the next IAP initialization and return Complete from the ProcessPurchase that is automatically triggered.
     
  41. Tx

    Tx

    Joined:
    Jul 4, 2012
    Posts:
    108
    I had one user pay from Google (let's say the 14 of this month).
    He got nothing back from the Server (probably he got disconnected) so he wrote after a day to our user support giving the GPA receipt code
    The GPA was checked in Google backend and was legit and the day/date was the one the user reported.
    Our support gave to this user the goods he had right to.
    At that moment in our database there was no reference to this GPA.
    A few days later the GPA was registered, was deemed as valid and all was ok.
    What I assumed is that app was only hybernated, never restarted for days. When user rebooted phone or something like that the IAP api read the pending transaction and fired it again.

    As we agreed before the problem is that Staff should have asked the user to restart her/his device. What I was thinking is If I could do something better using currently API or changing my flow.

    p.s.
    I tried to describe that problem in post #32 of this thread. As I said it seems very rare anyway.
     
  42. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Got it. Your first step is to reproduce the problem then take steps to address it. At this point you are just speculating what happened and making assumptions. It sounds like the transaction was not in pending and you may have a server issue. You need to test the purchase and retry scenario when your server is not available.
     
    Tx likes this.
  43. Tx

    Tx

    Joined:
    Jul 4, 2012
    Posts:
    108
    Unfortunately we couldn't ask this user for any log and it's not viable to log everything for everyone. Thanks again for your time, I'll try to stress all the system doing weird things. As usual in the 'normal' development all works ;)
     
  44. antonio_unity614

    antonio_unity614

    Joined:
    Nov 20, 2018
    Posts:
    1
    Hi, I am having another problem with the purchase related.
    The users of my game are processing a purchase but, when the native (Android) message appears, instead of continuing with the purchase, the user sets the app to the background. Then, after open again from the background, the native message disappear but the listeners (Process purchase or On Purchase failed) are not called. That means, that the purchase remains pending but I don't know about it.
    When I try to purchase again, I always get a purchase failed because: "OnPurchaseFailed() purchaseFailureReason = ExistingPurchasePending"

    I tried to call the method "ConfirmPendingPurchase" before to press purchase again, but it does nothing
    After restarting the game, the problem is solved.


    -Should I call again to the "UnityPurchasing.Initialize" in order to restore the store status?


    This is my purchase flow:
    upload_2020-11-23_17-21-13.png
    Then the user sets the app to background and the native dialog disappears but unity is not informing me that this happens.



    Thank you in advance.
     
  45. John_Corbett

    John_Corbett

    Joined:
    May 17, 2019
    Posts:
    151
    Hi @antonio_unity614 ,

    -Should I call again to the "UnityPurchasing.Initialize" in order to restore the store status?
    No, unfortunately calling Initialize again won't help you and it is not recommended to call it more than once in the lifetime of your application.

    Can you tell us which versions of IAP (Asset Store and Package Manager) you have, so that we can investigate this further?
     
  46. mustafa_d

    mustafa_d

    Joined:
    Dec 15, 2020
    Posts:
    11
    Is there any update on this? I have a similar problem. I'm able to listen
    Code (CSharp):
    1. PurchaseProcessingResult
    in the editor but when I build for Mac and make my purchase successfully,
    Code (CSharp):
    1.  
    2. public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args)
    3. {
    4.     if (String.Equals(args.purchasedProduct.definition.id, kProductIDNonConsumable, StringComparison.Ordinal))
    5.     {
    6.         DPBaseUser.Instance.IsPremium = true;
    7.         DPPopup.Instance.Show("Success",
    8.             "Purchase Success" + m_AppleExtensions.GetTransactionReceiptForProduct(args.purchasedProduct), "OK");
    9.  
    10.         Debug.Log(string.Format("ProcessPurchase: PASS. Product: '{0}'", args.purchasedProduct.definition.id));
    11.     }
    12.    
    13.     return PurchaseProcessingResult.Complete;
    14. }
    15.  
    is not working
     
  47. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Please describe "not working". Have you configured your products on the Mac store? Does it work on iOS? Place debug statements outside your if statement to confirm if it's being called.
     
  48. stenu2nd

    stenu2nd

    Joined:
    May 27, 2020
    Posts:
    3
    Similar, hopeless problem here. We have 6 consumable products, and after testing all of them in sandbox/iOS, they are all blocked by a transaction which is forever in pending state. I've even made
    IStoreListener.ProcessPurchase
    to simply return
    Complete
    all the time, with no luck.

    After a fresh install, Unity's
    PurchasingManager
    logs "already recorded transaction" for all six products, with no errors or warnings.

    After that, if I kill the app,
    ProcessPurchase
    is called for all products/transactions and on xcode debugger I can see the "Finishing transaction" being logged for each.

    Whenever I suspend and unsuspend, I see "DuplicateTransaction" error for all 6.

    I've also tried manually call
    IStoreController.ConfirmPendingPurchase
    for all transactions and products I can find, and I can 100% say this method does absolutely nothing.

    Can anyone think of any crazy theory that would cause this thing to only half work? I.e. purchases go through, receipts are received (for the 6 times I was able to test it) etc. but transactions just don't get closed.
     
  49. stenu2nd

    stenu2nd

    Joined:
    May 27, 2020
    Posts:
    3
  50. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    I tested yesterday afternoon US Pacific time without issue using IAP 3.0.2 on iOS. I tested using ConfirmPendingPurchase after returning Pending from ProcessPurchase and also tested returning Complete directly from ProcessPurchase without issue. I was able to continue to purchase the consumable product, and was prompted for the Sandbox password each time.