Search Unity

Question Proper flow for ConfirmPendingPurchase with server side validation

Discussion in 'Unity IAP' started by Paper-Admin, May 24, 2023.

  1. Paper-Admin

    Paper-Admin

    Joined:
    Sep 13, 2021
    Posts:
    5
    Unity 2020.3.38f1
    IAP version 4.4.1

    Hello!

    This is similar to a related thread: https://forum.unity.com/threads/solved-how-to-use-unity-iap-confirmpendingpurchase-properly.524646/

    However I was not able to come to the same conclusions as the original poster.

    I have ProcessPurchase call our server with the productID, transactionID, and receipt so our server can validate the purchase with apple. But we are having difficulty in how to properly call the ConfirmPendingPurchase with the intended product.

    Backstory:
    Initially when we took over the script for the client, they would append these products to a list called pendingProducts in the ProcessPurchase call.

    Product product = purchaseEventArgs.purchasedProduct;
    processingProducts.Add(product); //<-- List<Product>


    Then their script would take the callback from their server with the transactionID and then sift through their list of processingProducts to find the right one and then complete the transaction with ConfirmPendingPurchase.

    But this revealed that the purchaseEventArgs.purchasedProduct item that is given in ProcessPurchase is a mutable object. Where the purchasedProduct is actually passed to ProcessPurchase by reference. So the list that they had created would end up having all elements pointing to the most recently added element.

    ie:
    processingProducts.Add(banana); //<-- list content reads "banana"
    processingProducts.Add(apple); //<-- list content reads "apple,apple"
    processingProducts.Add(orange); //<-- list content reads "orange,orange,orange"


    ---------------------------------------------

    Now, it doesn't seem like this is the intended implementation based on the conversation in the provided old forum post. But we also were having trouble passing the product to the server as described there. Where we were able to serialize the Product information through Newtonsoft's json converter, but we can't turn it back into a product afterwards due to a lack of constructor. In the topic linked, it was mentioned that an intended flow was going to be posted with an example of how to do this reconversion back into a product step, but the OP finished off the topic with their own solution. (which doesn't work for us. When tested we still get the wrong transaction id using their method)


    -What is the intended way to give ConfirmPendingPurchase its product information?
    -If the intended flow is to send it to the server to have the server pass it back, what is the proper process to convert the json object back into a Product. (Which isnt possible via JsonUtility; as JsonUtility cant serialize properties. You have to use a third party tool like Newtonsoft's json convertor to serialize the product to get an actual json back)
     
    Last edited: May 24, 2023
  2. Baroni

    Baroni

    Joined:
    Aug 20, 2010
    Posts:
    3,261
    Hi,

    first off, there is no real need to add pending products to a "processingProducts" list. Instead, you would fire a separate (or combined, e.g. on app launch) receipt validation request to your server in ProcessPurchase and return ProcessProcessingResult.Pending.

    See here for a workflow graphic (bottom one):
    https://docs.unity3d.com/Packages/c...gPurchases.html#saving-purchases-to-the-cloud

    As long as the transaction is pending and your server has not confirmed it, ProcessPurchase will be called again for this product every time Unity IAP is initialized. Therefore, maintaining an extra list of products is not necessary, as mentioned above.

    When sending the receipt validation request to your server, you would do so in a separate task or coroutine which allows waiting for the result. I'm not sure what the issue is with receiving or parsing the correct response, since you control what your server returns back to the app? For example it could return a single (or list of) product ID, describing the verified purchases. So with that response, you could get the Product via StoreController.products.WithID(productID) as described in the thread you linked and call ConfirmPendingPurchase on that.

    (Source / Disclosure) I am running a server receipt validation service on my website.
     
  3. Paper-Admin

    Paper-Admin

    Joined:
    Sep 13, 2021
    Posts:
    5
    Similar to the scenario in the old forum post, what about scenarios where there are multiple separate transactions of the same product? In our case, in an auto-renewing subscription scenario where the user has "skipped" a month (ie, they paid for a monthly recurring subscription but did not sign in for one or more months, the product id will be the same but the transaction id would be different for each month. And the benefits for each month (bonus coins/tickets/etc) need to be applied and marked as complete.

    If I feed StoreController the WithID(productID), I don't have a guarantee that we are closing out the correct pending transaction. (This was the implementation implemented by OP in their topic)

    When we attempted to clear it that way, the returned product from the store controller would always be the same instance (not sure which specifically, but I want to say it was the last reported processed purchase) and thus it would not have the proper transaction ID that was processed by the server.

    This is where its weird to us that a Product has a transaction id. Where we'd imagine a Product being the raw product offering with its price and title and the like, but a Transaction should be a different thing that has a product instead.

    So queued multiple unique transactions of the same product is our scenario and source of issue (or perhaps a total misunderstanding of how it works)
     
  4. Baroni

    Baroni

    Joined:
    Aug 20, 2010
    Posts:
    3,261
    Why would you have multiple pending transactions of the same product? Users should not be able to purchase a product that is already pending, this should be prevented in the UI.

    In a scenario with multiple separate transactions of the same product, each will have a unique receipt, and your server can figure out if it is valid/active or not. If one of these transactions is valid, the user should be rewarded with the product.

    Either way, regarding subscriptions, your server should respond with the subscription state or store the current subscription state in the user's inventory on your server database. So when the user sends an "old" transaction to your server, nothing will happen, as it detects the expired state. And when the user "logs into" your app, it requests the current inventory from the database which may or may not contain an active subscription that was validated before for that user.

    A more complex approach is to also implement server-side notifications from the App Store which call into your server to update the user's inventory, in case a subscription was purchased, cancelled or expired, even when outside the app. This is what my service does too.
     
  5. Paper-Admin

    Paper-Admin

    Joined:
    Sep 13, 2021
    Posts:
    5
    Right, and in our case it's not that we are over-rewarding or having any trouble checking the validity of the receipt. Its not even an issue of the users double purchasing or having access to purchase UI they shouldn't. This happens at app start when IAP initializes and the receipts come in from Apple from their past transactions that have stacked up. But your response still doesn't tackle how it would close the other stacked transactions.

    User logs in
    IAP sees transaction 11552 for product 109 for last months subscription
    app sends the server a request to check if 11552 was cleared, marks as pending
    IAP sees transaction 11553 for product 109 for this months subscription
    app sends the server a request to check if 11553 was cleared, marks as pending
    Server responds that 11552 was cleared and has been rewarded properly. Currently returns that product 109 was cleared.
    App currently looks up product 109 via StoreController.WithID and gets transaction 11553. (wrong)
    ConfirmPurchase is called with product 109 that has transaction 115533.
    Server responds that 11553 was cleared and has been rewarded properly. Currently returns that product 109 was cleared.
    App currently looks up product 109 via StoreController.WithID and gets transaction 11553.
    Unity responds that product 109 with transaction id 11553 was already cleared and has nothing to do.

    So what happens to transaction 11552? How can we call the ConfirmPendingPurchase with the right product that has the correct transaction id?

    I have a json that has the right information. But it can't turn back into a product. If it could, I could give it that.
     
  6. Paper-Admin

    Paper-Admin

    Joined:
    Sep 13, 2021
    Posts:
    5
    Bump, still looking for information
     
  7. Paper-Admin

    Paper-Admin

    Joined:
    Sep 13, 2021
    Posts:
    5
    It's been over a month but there hasn't been any message from unity staff. Is there any information that I need to provide? Or any feedback on how this is intended to be used? Is this not the right location for this question?