Destructive Changes Discovered When Upgrading from Unity IAP 4.12 to 5.0.0pre5 – Feedback and Questions

Hello Unity Team and Community,

I’m currently maintaining a project using code-based IAP (not codeless). During our upgrade testing from Unity IAP 4.12 to 5.0.0pre5, we discovered several changes that appear to be breaking or at least significantly disruptive. I would like to share our findings and ask for clarification or guidance regarding these issues:


1. ConfigurationBuilder.Instance

  • Observation:
    In Unity IAP 4.12, each call to ConfigurationBuilder.Instance returned a new instance, meaning that previously added products via AddProduct were not retained. However, in 5.0.0pre5, ConfigurationBuilder.Instance now returns a singleton instance. As a result, products registered during a previous initialization persist. If we subsequently call AddProduct with the same product, we encounter a “Same key” error from ProductDetailConverter::ConvertOnQueryProductDetailsResponse.

  • Current Workaround:
    Using the new public constructor introduced in 5.0.0 (instead of the Instance method) effectively clears the previous state. However, we’re unsure if this approach is the intended usage, and it took considerable time to diagnose the root cause.

  • Request:
    Could you please clarify if this singleton behavior is intentional? We believe that avoiding a singleton—so that each initialization starts with a clean slate—might be more robust, especially for scenarios where a product list needs to be refreshed.


2. UnityPurchasing.Initialize and IStoreController.FetchAdditionalProducts

  • Observation:
    Both UnityPurchasing.Initialize and IStoreController.FetchAdditionalProducts internally utilize methods such as ProductService::AddProductsUpdateAction and AddProductsFetchFailedAction. However, since the delegates are passed as new instances each time, the corresponding event removal using “-=” does not work as intended. Consequently, each call to these methods accumulates event handlers, which leads to unintended callbacks and eventually system instability.

  • Issue:
    There is no public method provided to clear or reset these event handlers (i.e., for OnProductsUpdated and OnProductsFetchFailed), forcing developers to modify the IAP code directly to prevent cascading issues.

  • Request:
    We would appreciate it if the design could be revisited to either allow clearing of these events or be reset them internally by Unity IAP.


3. FetchProductsUseCase::m_ActiveRequest

  • Observation:
    The member m_ActiveRequest is used to indicate whether a product fetch is still in progress. Within OnProductRetrieved, the method ProcessRetrievedProductsAndInvokeCallbacks is called first, and only afterward is m_ActiveRequest set to null. This means that if InvokeSuccess is called inside ProcessRetrievedProductsAndInvokeCallbacks—and that callback, in turn, triggers another FetchProducts call—it will be treated as if the previous fetch is still active, resulting in an error.

  • Note:
    This error did not occur in version 4.12 and can be easily overlooked, which makes it particularly concerning.

  • Request:
    We ask that this logic be reviewed to ensure that successive fetch calls can be handled gracefully without false-positive errors regarding an active request.

Hi b3lypscfcon,

Thanks for bringing these concerns to our attention! The team has reviewed your feedback, and here is what we have gathered and suggest:

1. Singleton Behavior in ConfigurationBuilder.Instance:

The new singleton behavior for ConfigurationBuilder.Instance is intentional. This change was made to improve persistence across initializations and streamline certain workflows. It has always been recommended to never call UnityPurchasing.Initialize more than once.

For now, your workaround (using the new constructor directly) is valid. However, please note that we may not expose this constructor in the future, as it could lead to another breaking change.

Could you share more details on your use case for resetting the product list? This will help us ensure the best solution for all scenarios.

2. Event Handlers Accumulating in UnityPurchasing.Initialize and IStoreController.FetchAdditionalProducts:

We are aware of the issue with event handlers accumulating in UnityPurchasing.Initialize and IStoreController, which can lead to instability.
This is something we are actively working on resolving on our end and there is currently no good workaround. In the meantime, you could try passing null after the first invocation to avoid duplicates.

3. Issue with m_ActiveRequest in FetchProductsUseCase:

The issue with m_ActiveRequest in FetchProductsUseCase is specific to version 5.0.0, and we’re addressing it in the upcoming 5.0.0-pre.6 release.


Again, we appreciate you bringing this to our attention, and we are committed to making these changes to improve the reliability and ease of use of Unity IAP.

Best regards

Hello @vd_unity

Thank you for your detailed response. I’d like to address a few points:

  1. Singleton Behavior in ConfigurationBuilder.Instance
    I understand that the singleton behavior is intentional, and that using the public constructor to create a new instance effectively clears the previous state. I also recognize that to maintain singleton consistency, the public constructor may not remain available indefinitely.

Regarding the question, “Could you share more details on your use case for resetting the product list?” — we understand that since UnityPurchasing.Initialize() is recommended to be called only once, there should normally be no need to call the ConfigurationBuilder constructor. However, some long-running projects, particularly those ported from Prime31 to Unity IAP, have maintained the practice of re-calling the initialization process to refresh the product list. As a result, there are sequences where UnityPurchasing.Initialize() is called multiple times. While we acknowledge your concerns and plan to adjust our implementation so that UnityPurchasing.Initialize() is only called once, for now we would like to retain our current implementation for the upgrade to 5.0.0. We appreciate your concerns and the offer of support.

  1. Event Handlers Accumulating in UnityPurchasing.Initialize and IStoreController.FetchAdditionalProducts
    I understand that this issue is slated for future fixes. In the meantime, we are working around it by overwriting the event registration (using “=” instead of “+=”) to prevent the accumulation of duplicate event handlers.

  2. Issue with m_ActiveRequest in FetchProductsUseCase
    Thank you for acknowledging this issue. We look forward to a fix in a future update.

One more thing I noticed while reviewing the IAP internals: It appears that the return value of IStoreListener::ProcessPurchase (PurchaseProcessingResult) is not used anywhere. I was under the impression that this return value was necessary for managing transaction state. Is this behavior intentional, or might it be an oversight?

Thank you again for your attention and support.

Best regards

Hey @b3lypscfcon !

Thanks for the detailed response!
We truly appreciate the insights into your use case and the steps you are taking to adapt to IAP 5.0.0.

  1. Singleton Behavior in ConfigurationBuilder.Instance
    That makes sense! Given the history of ported projects, multiple calls to UnityPurchasing.Initialize() may have been a necessary part of your workflow. Your plan to gradually adjust the implementation while upgrading to 5.0.0 is completely understandable.

  2. Event Handlers in UnityPurchasing.Initialize and IStoreController.FetchAdditionalProducts
    This is something we are considering for future improvements. Your workaround of using = instead of += to prevent handler accumulation is a good interim solution.

  3. Issue with m_ActiveRequest in FetchProductsUseCase
    Thanks for your patience on this! We will update when we have a fix.

  4. Regarding IStoreListener::ProcessPurchase (PurchaseProcessingResult)
    In IAP 5.0.0, event-based handling replaces ProcessPurchase, making its return value obsolete in the new flow. Instead, purchases are explicitly confirmed via ConfirmPendingPurchase.
    The new approach ensures that transactions are completed explicitly, rather than relying on the return value of ProcessPurchase.
    For reference, you can check the updated API documentation:
    IAPListener - Unity Docs

Workaround for Backward Compatibility (4.x → 5.x)
If you need to maintain compatibility while transitioning, you can manually confirm the purchase before returning Pending, as suggested by our main dev:

var ProcessPurchaseResult;
[your code]
if (PurchaseProcessingResult.Complete == ProcessPurchaseResult)
        ConfirmPendingPurchase(product);
return PurchaseProcessingResult.Pending;

This workaround aligns with the new approach while preventing unintended side effects in your current implementation.

Please let us know if this helps!
Best regards

Hi @vd_unity,

Thank you for your detailed response. I’d like to address a couple of points:

  1. Singleton Behaviour in ConfigurationBuilder.Instance
    Thanks for understanding our perspective. In cases like ours, where the scope of impact necessitates calling UnityPurchasing.Initialize() more than once, it might be helpful to include a note in the 5.0.0 upgrade manual (or as a comment in the code) indicating that using the ConfigurationBuilder constructor as a workaround to avoid errors from multiple calls to UnityPurchasing.Initialize() remains a valid option.

  2. Regarding IStoreListener::ProcessPurchase
    Your suggested workaround effectively replicates the processing logic that was originally handled in PurchasingManager::ProcessPurchaseIfNew within IStoreListener::ProcessPurchase to maintain compatibility. If IStoreListener::ProcessPurchase is intended to trigger ConfirmPendingPurchase via ProcessPurchaseIfNew, then the workaround seems appropriate. However, if IStoreListener::ProcessPurchase always returns PurchaseProcessingResult.Pending, the workaround might not be necessary.
    Either way, thank you for sharing the workaround.

Once again, thank you very much for your detailed comments and for providing the workaround. Your support is greatly appreciated.

Best regards

Hi @b3lypscfcon,

Thanks for your thoughtful response! We really appreciate your insights and the feedback on the upgrade experience.

1. Singleton Behavior in ConfigurationBuilder.Instance

You are bringing a great point! Given that some projects may still require multiple calls to UnityPurchasing.Initialize(), adding a note in the 5.0.0 upgrade manual (or even as a comment in the code) about using the ConfigurationBuilder constructor as a workaround could help avoid confusion. We will take this feedback into account as improvement to the documentation.

2. IStoreListener.ProcessPurchase and ConfirmPendingPurchase

You are absolutely right - our suggested workaround essentially mimics the behavior of PurchasingManager::ProcessPurchaseIfNew in 4.12 to maintain compatibility.

I just checked, and in 4.12, ProcessPurchaseIfNew first verifies whether the transaction has already been recorded. If it has not, it calls IStoreListener.ProcessPurchase(). If the result is PurchaseProcessingResult.Complete, it then calls ConfirmPendingPurchase().

key part:

if (m_Listener?.ProcessPurchase(p) == PurchaseProcessingResult.Complete)
{
    ConfirmPendingPurchase(product);
}

Since this logic was removed in 5.0.0 in favor of explicit confirmation via OnPendingOrderUpdatedAction, the workaround we suggested restores the same behavior for backwards compatibility.

To your point, if ProcessPurchase always returns PurchaseProcessingResult.Pending, then the workaround would not be needed. However, in cases where the transaction must be explicitly confirmed, this approach ensures consistency with the old implementation while keeping it future-proof.

Also, big thanks to our main dev, @yannick for his insights and help in refining this solution!

Thanks again for the valuable discussion - let us know if you have any further questions!

Best regards

Hi @vd_unity,

First of all, thank you for releasing IAP 5.0.0-pre.6.
We have begun integration testing on our side and are pleased to see that several of the issues we reported earlier have already been fixed—thanks!


Remaining callback issue carried over from the original report

Scenario
UnityPurchasing.Initialize() is invoked more than once
(this still happens in a legacy flow within our codebase).

Expected behaviour in 4.12
After the first successful product fetch, the internal delegates are
removed, so subsequent Initialize() calls do not trigger additional
IStoreListener.OnInitialized(…) events.

Observed behaviour in 5.0.0-pre.6
In UnityPurchasing::AddProductServiceListeners the
lambdas remain subscribed, which causes multiple OnInitialized callbacks
on the same listener instance.

Minimal repro steps

  1. Call UnityPurchasing.Initialize(listener, builder).
  2. Wait for listener.OnInitialized(…).
  3. Call UnityPurchasing.Initialize(listener, builder) again.
  4. Result: OnInitialized is invoked a second time (this did not happen in 4.12).

Proposed fix (tested locally – mirrors FetchAdditionalProducts pattern)

    static void AddProductServiceListeners(IStoreListener storeListener,
                                           IProductService productService)
    {
        Action<List<Product>> onFetched = null;
        Action<ProductFetchFailed> onFailed = null;

        onFetched = _ =>
        {
            productService.OnProductsFetched     -= onFetched;
            productService.OnProductsFetchFailed -= onFailed;
            storeListener.OnInitialized(m_PurchasingManager,
                                        new ExtensionProvider());
        };

        onFailed = failed =>
        {
            productService.OnProductsFetched     -= onFetched;
            productService.OnProductsFetchFailed -= onFailed;
            storeListener.OnInitializeFailed(
                InitializationFailureReason.PurchasingUnavailable,
                failed.FailureReason);
        };

        productService.OnProductsFetched     += onFetched;
        productService.OnProductsFetchFailed += onFailed;
    }

With this change, our legacy re-initialisation path behaves identically to 4.12.

Questions

  1. Would you consider integrating a similar unsubscribe logic into the official package?
  2. If not, could you confirm that the above workaround is safe and forward-compatible?

Thanks again for the quick turnaround on pre.6. We appreciate your support and will report any additional issues we discover during testing as soon as possible.

Best regards

Hi @Unity IAP Team,

Could someone please confirm whether this issue is on your radar?
We’ve been waiting for about a week.

Thank you for your time!

Hi @b3lypscfcon,
I can confirmed that the issue is being tracked.
We will investigate it and come back to you with an update.
Thanks again for your thorough investigation !

@ArthurCarriere
Thanks for taking the time to review this item.
We’d really like to see it fixed for backward-compatibility with 4.12.
Many legacy systems still rely on the previous behaviour, and preserving that consistency would make the migration to 5.x much smoother.
If a compatibility patch is planned, please let us know.