Search Unity

How to confirm a pending purchase

Discussion in 'Unity IAP' started by ValienteDS, May 23, 2019.

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

    ValienteDS

    Joined:
    Apr 7, 2019
    Posts:
    3
    So basically, I need to verify the payment on the server side and then give the users their rewards for IAPs. So in the "ProcessPurchase" method, I'm calling another method to apply verification on my server, then returning a boolean depending on if everything went good or not.
    The problem is if I return "PurchaseProcessingResult.Pending" before confirming the purchase, I don't know how to trigger the purchase method again. I believe I need to use "ConfirmPendingPurchase" somewhere but can't seem to do it right.

    Beginning of ProcessPurchase

    Code (CSharp):
    1. public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args)
    2.     {
    3.         // A consumable product has been purchased by this user.
    4.         if (String.Equals(args.purchasedProduct.definition.id, Battlecoins_01, StringComparison.Ordinal))
    5.         {
    6.             success = isProdValid(Battlecoins_01);
    7.             if (success)
    8.             {
    9.                 Debug.Log(string.Format("ProcessPurchase: PASS. Product: '{0}'", args.purchasedProduct.definition.id));
    10.                 ItemHandler.Instance.SetBattleCoins(ItemHandler.Instance.BattleCoins + 350);
    11.                 //m_StoreController.ConfirmPendingPurchase(args.purchasedProduct);
    12.                 return PurchaseProcessingResult.Complete;
    13.             }
    14.             //Supposed Price: 1.50£
    Payment verify method

    Code (CSharp):
    1. public bool isProdValid(string prodId)
    2.     {
    3.         Product prod = m_StoreController.products.WithID(prodId);
    4.  
    5. #if UNITY_ANDROID
    6.         var purchaseReceipt = JsonUtility.FromJson<GooglePurchaseReceipt>(prod.receipt);
    7.         var purchasePayload = JsonUtility.FromJson<GooglePurchasePayload>(purchaseReceipt.Payload);
    8.         var inAppJsonData = JsonUtility.FromJson<GooglePurchaseJson>(purchasePayload.json);
    9.  
    10.         inAppPurchaseData = purchasePayload.json;
    11.         inAppDataSignature = purchasePayload.signature;
    12.         json = inAppJsonData;
    13. #endif
    14.  
    15.         if (prod.hasReceipt && prodId.Contains("battlecoins"))
    16.         {
    17.             //m_StoreController.ConfirmPendingPurchase(prod); ?? (No Pending Purchase err)
    18.             //m_StoreController.InitiatePurchase(prod); ?? (Duplicate Purchase err)
    19.             if (ApiManager.PurchaseAPI.OnBuyCompleted != null) ApiManager.PurchaseAPI.OnBuyCompleted(true, prodId, prod.receipt, inAppDataSignature, prod);
    20.             return true;
    21.         }
    22.         else
    23.         {
    24.             ApiManager.PurchaseAPI.OnBuyCompleted(false, prodId, "", "", prod);
    25.             return false;
    26.         }
    27.     }
     
  2. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    ProcessPurchase will be triggered automatically on next app launch for each pending purchase.
     
  3. MaximPP

    MaximPP

    Joined:
    Jan 26, 2019
    Posts:
    76
    What if i'm not ready to handle ProcessPurchase during application startup? I need some pre-initialization and full loading of the application to show the user a GUI window. Can I call ProcessPurchase for pending purchases when it's convenient for me?
     
  4. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    You don't call ProcessPurchase, the Google billing library API calls it automatically. You can always store the product object for later, I do this in the Sample IAP Project v1 https://forum.unity.com/threads/sample-iap-project.529555 .
     
    MaximPP likes this.
  5. ozanutriodor

    ozanutriodor

    Joined:
    Nov 11, 2019
    Posts:
    9
    Hi, I know this is an old topic but similar issue still going on. I am a colleague of ValienteDS and we are confronting a very similar issue.

    We have our own server implementation that validates a purchase and returns success if it is valid. And we call ConfirmPendingPurchase method if server returns success. Basically flow;

    ProcessPurchase -> return pending -> send payload to server -> wait for success -> call ConfirmPendingPurchase

    But the flow fails at "wait for success" step. Because if we return pending on ProcessPurchase method, the server fails to validate the transaction id. I think it is because the purchase being stalled since its still pending. So it's not a valid purchase at that moment.

    Can you please enlighten us about what is the correct flow?
     
  6. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Please show the code you are using. The transaction status has no bearing on validating the transactionID. And you should be checking on you server BEFORE you return Pending in your ProcessPurchase method. But it shouldn't matter. And what are you "waiting" on, is your server down or similar? Is this your server? And you mention you "think it is because.." can you elaborate? Find out for sure by proper debugging and remove all doubt. Do you mean if you return Complete as a test, then is succeeds? If you remove your server check and do local receipt validation only (or none), do your purchases succeed as expected?
     
  7. ozanutriodor

    ozanutriodor

    Joined:
    Nov 11, 2019
    Posts:
    9
    The ProcessPurchase method;

    Code (CSharp):
    1. public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args)
    2.         {
    3.             var purchasedProduct = args.purchasedProduct;
    4.             var productId = purchasedProduct.definition.id;
    5.             var product = storeController.products.WithID(productId);
    6.  
    7.             Debug.Log($"ProcessPurchase: Product: '{productId}'");
    8.  
    9.             if (product != null)
    10.             {
    11.                 Debug.Log($"ProcessPurchase: PASS. Product: '{productId}'");
    12.  
    13.                 var wrapper = (Dictionary<string, object>) MiniJSON.Json.Deserialize(purchasedProduct.receipt);
    14.                 if (wrapper == null)
    15.                 {
    16.                     Debug.Log("Receipt couldn't be decoded");
    17.                     return PurchaseProcessingResult.Pending;
    18.                 }
    19.  
    20.                 var payload = (string) wrapper["Payload"];
    21.  
    22.                 pendingProducts.Add(purchasedProduct);
    23.                 OnProductPurchased?.Invoke(purchasedProduct, payload);
    24.                 return PurchaseProcessingResult.Pending;
    25.             }
    26.  
    27.             Debug.Log($"ProcessPurchase: FAIL. Unrecognized product: '{productId}'");
    28.             return PurchaseProcessingResult.Pending;
    29.         }
    And in OnProductPurchased;


    Code (CSharp):
    1. public void StartPurchaseValidation(Product purchasedProduct, string payload)
    2.         {
    3.             Debug.LogWarning($"StartPurchaseValidation");
    4.  
    5.             if (!purchasedProduct.hasReceipt)
    6.             {
    7.                 Debug.LogError($"Product has empty receipt!");
    8.                 return;
    9.             }
    10.  
    11.             var centPrice = (int)purchasedProduct.metadata.localizedPrice * 100;
    12.  
    13. #if UNITY_ANDROID
    14.             ShopService.ValidateGooglePlayProductPurchase(
    15.                 purchasedProduct.transactionID,
    16.                 centPrice,
    17.                 purchasedProduct.metadata.isoCurrencyCode,
    18.                 payload,
    19.                 res =>
    20.                 {
    21.                     Debug.LogWarning($"Purchase result: {res.Success} for TransactionID: {res.TransactionId}");
    22.                     if (res.Success)
    23.                     {
    24.                         var product = pendingProducts.Find(p => p == purchasedProduct);
    25.                         if (product != null)
    26.                         {
    27.                             storeController.ConfirmPendingPurchase(product);
    28.                             pendingProducts.Remove(product);
    29.                         }
    30.  
    31.                         GameAnalyticsService.SendPurchaseSuccessAnalytic(purchasedProduct.definition.id, purchasedProduct.metadata.localizedTitle, purchasedProduct.metadata.isoCurrencyCode, purchasedProduct.metadata.localizedPrice);
    32.  
    33.                         Debug.LogWarning($"Success product: {purchasedProduct.definition.id}");
    34.                         if (string.Equals(purchasedProduct.definition.id, preRegRewardProductId))
    35.                         {
    36.                             SecurePlayerPrefs.SetBool(preRegRewardPrefsId, true);
    37.                             DialogStack.Push<IPreRegRewardDialog>();
    38.                         }
    39.                     }
    40.                     else
    41.                     {
    42.                         GameAnalyticsService.SendPurchaseFailAnalytic();
    43.                     }
    44.                 });
    45. #elif UNITY_IOS
    46.             ShopService.ValidateAppStoreProductPurchase(
    47.                 purchasedProduct.transactionID,
    48.                 centPrice,
    49.                 purchasedProduct.metadata.isoCurrencyCode,
    50.                 purchasedProduct.receipt,
    51.                 res =>
    52.                 {
    53.                     Debug.LogWarning($"Purchase result: {res.Success} for TransactionID: {res.TransactionId}");
    54.                     if (res.Success)
    55.                     {
    56.                         GameAnalyticsService.SendPurchaseSuccessAnalytic(purchasedProduct.definition.id, purchasedProduct.metadata.localizedTitle, purchasedProduct.metadata.isoCurrencyCode, purchasedProduct.metadata.localizedPrice);
    57.                     }
    58.                     else
    59.                     {
    60.                         GameAnalyticsService.SendPurchaseFailAnalytic();
    61.                     }
    62.                 });
    63. #endif
    64.         }
     
  8. ozanutriodor

    ozanutriodor

    Joined:
    Nov 11, 2019
    Posts:
    9
    By waiting I mean the response from server. Since it's not an immediate action, we send the request, server validates and returns a response. In that response if it is a success (res.Success) we call ConfirmPendingPurchase.
     
  9. ozanutriodor

    ozanutriodor

    Joined:
    Nov 11, 2019
    Posts:
    9
    Also, after couple of minutes failing the validation. Google sends us refund mails with the reason "purchase not acknowledged".
     
  10. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    This code is not familiar and I might not expect to work properly. You are not checking the return value of your server call. You don't need a list of pending products. It appears you are invoking a callback from a callback. As you didn't address my questions, I would suggest you compare to the Sample IAP Project v3. Get it working as is, then add your server customizations https://forum.unity.com/threads/sample-iap-project.529555/#post-7922275
     
  11. ozanutriodor

    ozanutriodor

    Joined:
    Nov 11, 2019
    Posts:
    9
    Sure it won't be familiar. It's shaped to fit our system. By server I mean our server yes. Custom implemented server. It takes the receipt, and validates the purchase and returns response to client. But it returns fail to client for some reason. It successfully validates the purchase if we return "PurchaseProcessingResult.Complete" before sending the receipt to our server for validation. And yes, the purchase succeed without sending to our server.

    We are checking the return value of our server call in;

    Code (CSharp):
    1. if (res.Success)
    2.                     {
    3.                         var product = pendingProducts.Find(p => p == purchasedProduct);
    4.                         if (product != null)
    5.                         {
    6.                             storeController.ConfirmPendingPurchase(product);
    7.                             pendingProducts.Remove(product);
    8.                         }
    9.                         GameAnalyticsService.SendPurchaseSuccessAnalytic(purchasedProduct.definition.id, purchasedProduct.metadata.localizedTitle, purchasedProduct.metadata.isoCurrencyCode, purchasedProduct.metadata.localizedPrice);
    10.                         Debug.LogWarning($"Success product: {purchasedProduct.definition.id}");
    11.                         if (string.Equals(purchasedProduct.definition.id, preRegRewardProductId))
    12.                         {
    13.                             SecurePlayerPrefs.SetBool(preRegRewardPrefsId, true);
    14.                             DialogStack.Push<IPreRegRewardDialog>();
    15.                         }
    16.                     }
    and it doesn't goes in this if, if we return the ProcessPurchase result as PurchaseProcessingResult.Pending.
     
  12. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Got it, same suggestions apply. By your statement just now, you are checking on the server before returning Pending, which is correct. So the transaction status would have no bearing on your server handling. And I trust you are only doing receipt validation and not server side fulfillment (calling the Google billing API on your server)
     
  13. ozanutriodor

    ozanutriodor

    Joined:
    Nov 11, 2019
    Posts:
    9
    Yes we are only doing receipt validation on server. But first one is wrong. We check server after returning Pending. I mean, we have to complete the function right? We can't block the ProcessPurchase. We return Pending, and wait for server check.
     
  14. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    So you have confirmed it works as expected only if you return Complete? How do you know that the server isn't processing in the milliseconds before (or after) Pending is returned? In other words, it shouldn't matter. A Pending receipt is just as valid as a Complete receipt (the purchaseState in the receipt). How is your server failing? Do you mean it's returning False, or is there an exception?
     
  15. ozanutriodor

    ozanutriodor

    Joined:
    Nov 11, 2019
    Posts:
    9
    Hi, we made some progress but the issue still exists. We noticed that validation on our server works if we make purhcase with IAP package version 3.2.3. As you have said, it wasn't related to if we return Pending or Complete. But ProcessPurchase method doesn't even called for promo code redeeming. We thought that it may be related to we have downgraded the package. We upgrade the package again to 4.4.0 and redeemed a promo code on google play and opened the game. There were no ProcessPurchase method call again. Are there any possible reason that a redeemed product may stall at some point on Google or on Unity?
     
  16. ozanutriodor

    ozanutriodor

    Joined:
    Nov 11, 2019
    Posts:
    9
    And do you know on which reasons Google may not send ProcessPurchase event? We are now trying on a completely new phone and on a completely new gmail account. We have redeemed the related iap product by entering promo code to google play. And no ProcessPurchase method call at the moment. We are trying to figure out this issue.
     
  17. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    We do not yet handle Google Play points or promo codes.
     
  18. ozanutriodor

    ozanutriodor

    Joined:
    Nov 11, 2019
    Posts:
    9
    Do Unity IAP supports pre-registration rewards? I couldn't find anything related to that.
     
  19. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    This thread is going off topic so I will close it now. Please open a new thread and describe what you mean by pre-registration rewards.
     
Thread Status:
Not open for further replies.