Search Unity

  1. We've closed the job boards. If you're looking for work, or looking to hire check out Unity Connect. You can see more information here.
    Dismiss Notice
  2. Unity 2017.3 has arrived! Read about it here.
    Dismiss Notice
  3. Want to see the most recent patch releases? Take a peek at the patch release page.
    Dismiss Notice

IAP Restore Purchases bug in Live version

Discussion in 'Unity IAP' started by arandono, Jan 11, 2018.

  1. arandono

    arandono

    Joined:
    Dec 4, 2014
    Posts:
    22
    I have a bug in my project that I have been trying to track down to no avail. Our app uses Unity's IAP system for a subscription purchase. The iOS version has a Restore Purchases button using Unity's RestorePurchases() method. I have tested the app out extensively in TestFlight and Sandbox Environment, and everything worked as designed. I submitted the project to the Apple store and it was approved and is now live.

    In the Live version there the app crashes every time the user presses the Restore Purchases button. I've contacted Apple's Developer Support and they are unsure of where the bug is coming from, but they believe it is a Unity bug. I have symbolicated crash logs, and I don't see any reference to the custom methods I have built in. I've attached screenshots of the crash logs here.

    I've submitted a bug report to Unity, but I haven't had a response in several days. As this is a live version, it is critical that I get this resolved right away.

    In short, I'm stuck. Does anyone have any insight here?
     

    Attached Files:

  2. ap-unity

    ap-unity

    Unity Technologies

    Joined:
    Aug 3, 2016
    Posts:
    782
  3. arandono

    arandono

    Joined:
    Dec 4, 2014
    Posts:
    22
    I was using one of the examples Unity has posted as a template. This example has a RestorePurchases() method. In the method, it uses RestoreTransactions(), which is in Unity IAP. As for the IAP version, I've since updated to 1.15.0, so the version I was using was the version just before that.

    The code is directly from from Unity's Manual: https://docs.unity3d.com/Manual/UnityIAPRestoringTransactions.html

    I've modified the actions to my needs. Here's my actual code:

    Code (CSharp):
    1. // Restore purchases previously made by this customer. Some platforms automatically restore purchases, like Google.
    2.     // Apple currently requires explicit purchase restoration for IAP, conditionally displaying a password prompt.
    3.     public void RestorePurchases()
    4.     {
    5.         subscriptionSceneControls SSC = subscriptionSceneControls.instance;
    6.  
    7.         if(SSC) SSC.activateSubscriptionButtons(false);
    8.  
    9.         // If Purchasing has not yet been set up ...
    10.         if (!IsInitialized())
    11.         {
    12.             // ... report the situation and stop restoring. Consider either waiting longer, or retrying initialization.
    13.             if(SSC) SSC.showText(SubscriptionSceneText.purchaseFailed);
    14.             if(SSC) SSC.showYesNoButtons(false);
    15.             if(SSC) SSC.activateSubscriptionButtons(true);
    16.             Debug.Log("RestorePurchases FAIL. Not initialized.");
    17.             return;
    18.         }
    19.  
    20.  
    21.         // If we are running on an Apple device ...
    22.         if (Application.platform == RuntimePlatform.IPhonePlayer || Application.platform == RuntimePlatform.OSXPlayer)
    23.         {
    24.             // ... begin restoring purchases. Display message to user here that we are fetching their receipt.
    25.             Debug.Log("RestorePurchases started ...");
    26.  
    27.             if(SSC) SSC.showText(SubscriptionSceneText.restoringPurchases);
    28.  
    29.             // Fetch the Apple store-specific subsystem.
    30.             var apple = m_StoreExtensionProvider.GetExtension<IAppleExtensions>();
    31.             // Begin the asynchronous process of restoring purchases. Expect a confirmation response in
    32.             // the Action<bool> below, and ProcessPurchase if there are previously purchased products to restore.
    33.             apple.RestoreTransactions((result) => {
    34.                 // The first phase of restoration. If no more responses are received on ProcessPurchase then
    35.                 // no purchases are available to be restored.
    36.                 Debug.Log("RestorePurchases continuing: " + result + ". If no further messages, no purchases available to restore.");
    37.  
    38.                 bool isActive = false;
    39.  
    40.                 if(result)
    41.                 {
    42.                     Debug.Log("RestorePurchases: Restoring Purchases. Result = " + result);
    43.  
    44.                     // Check the expiration date on the restored receipt to allow access to figure
    45.                     if(IsInitialized())
    46.                     {
    47.                         Product product = m_StoreController.products.WithID(figureMonthlySubscription);
    48.                         string receiptString = product.receipt;
    49.                         CrossPlatformValidator validator = new CrossPlatformValidator(GooglePlayTangle.Data(), AppleTangle.Data(), Application.identifier);
    50.                         IPurchaseReceipt[] receipts = validator.Validate(receiptString);
    51.                         IPurchaseReceipt mostRecent = findMostRecentReceipt(receipts);
    52.                         saveReceiptsToFile(product);
    53.                         isActive = isSubscriptionActive(mostRecent);
    54.                     }
    55.                 }
    56.                 else
    57.                 {
    58.                     if(SSC) SSC.showText(SubscriptionSceneText.purchaseFailed);
    59.                     if(SSC) SSC.showYesNoButtons(false);
    60.                 }
    61.  
    62.                 if(isActive == true)
    63.                 {
    64.                     allowAccessToFigure = true;
    65.                 }
    66.                 else if(isActive == false)
    67.                 {
    68.                     // Display pop-up message saying that the we don't have an activeSubscription on record
    69.                     if(SSC) SSC.showText(SubscriptionSceneText.noReceipts);
    70.                     if(SSC) SSC.showYesNoButtons(false);
    71.                 }
    72.  
    73.                 //waiting = false;
    74.  
    75.             });
    76.  
    77.  
    78.         }
    79.         // Otherwise ...
    80.         else
    81.         {
    82.             // We are not running on an Apple device. No work is necessary to restore purchases.
    83.             if(SSC) SSC.showText(SubscriptionSceneText.purchaseFailed);
    84.             if(SSC) SSC.showYesNoButtons(false);
    85.             Debug.Log("RestorePurchases FAIL. Not supported on this platform. Current = " + Application.platform);
    86.             //waiting = false;
    87.         }
    88.  
    89.         if(SSC) SSC.activateSubscriptionButtons(true);
    90.     }
    91.  
    92. }
    93.  

    The custom code I've added to show text and buttons is pretty straightforward. Here's the showText code:

    Code (CSharp):
    1. // Use this along with the SubscriptionSceneText enum to show whatever text needs to be shown
    2.     public GameObject showText(SubscriptionSceneText text)
    3.     {
    4.         Debug.Log("showText called for " + text);
    5.  
    6.         #if UNITY_ANDROID
    7.         if(text == SubscriptionSceneText.subscribe) // Just making sure that the subscribe button is active
    8.         {
    9.             subscriptionSceneControls.instance.subscribeButton.SetActive (true);
    10.         }
    11.         #endif
    12.  
    13.         if(SceneManager.GetActiveScene() != SceneManager.GetSceneByName("Subscription Purchase Scene"))
    14.         {
    15.             Debug.Log("Can't show subscription scene text outside of Subscription Purchase Scene!");
    16.             return null;
    17.         }
    18.  
    19.         if(subscribeText)                 subscribeText.SetActive(false);
    20.         if(purchaseInfoText)             purchaseInfoText.SetActive(false);
    21.         if(connectToInternetText)         connectToInternetText.SetActive(false);
    22.         if(noReceiptsText)                 noReceiptsText.SetActive(false);
    23.         if(grabbingReceiptText)         grabbingReceiptText.SetActive(false);
    24.         if(communicatingWithStoreText)     communicatingWithStoreText.SetActive(false);
    25.         if(restoringPurchasesText)         restoringPurchasesText.SetActive(false);
    26.         if(purchaseFailedText)             purchaseFailedText.SetActive(false);
    27.         if(paymentDeclinedText)         paymentDeclinedText.SetActive(false);
    28.         if(promptRestoreText)             promptRestoreText.SetActive(false);
    29.         if(productUnavailableText)         productUnavailableText.SetActive(false);
    30.         if(goFigureText)                 goFigureText.SetActive(false);
    31.         if(initializationFailureText)    initializationFailureText.SetActive(false);
    32.         if(connectToVerifyText)            connectToVerifyText.SetActive(false);  
    33.  
    34.         GameObject textToShow = null;
    35.  
    36.         if(text == SubscriptionSceneText.none)
    37.             textToShow = null;
    38.         else if(text == SubscriptionSceneText.purchaseInfo)
    39.             textToShow = purchaseInfoText;
    40.         else if(text == SubscriptionSceneText.subscribe)
    41.             textToShow = subscribeText;
    42.         else if(text == SubscriptionSceneText.connectToInternet)
    43.             textToShow = connectToInternetText;
    44.         else if(text == SubscriptionSceneText.noReceipts)
    45.             textToShow = noReceiptsText;
    46.         else if(text == SubscriptionSceneText.grabbingReceipt)
    47.             textToShow = grabbingReceiptText;
    48.         else if(text == SubscriptionSceneText.communicatingWithStore)
    49.             textToShow = communicatingWithStoreText;
    50.         else if(text == SubscriptionSceneText.restoringPurchases)
    51.             textToShow = restoringPurchasesText;
    52.         else if(text == SubscriptionSceneText.purchaseFailed)
    53.             textToShow = purchaseFailedText;
    54.         else if(text == SubscriptionSceneText.paymentDeclined)
    55.             textToShow = paymentDeclinedText;
    56.         else if(text == SubscriptionSceneText.promptRestore)
    57.             textToShow = promptRestoreText;
    58.         else if(text == SubscriptionSceneText.productUnavailable)
    59.             textToShow = productUnavailableText;
    60.         else if(text == SubscriptionSceneText.goFigure)
    61.             textToShow = goFigureText;
    62.         else if(text == SubscriptionSceneText.initializationFailure)
    63.             textToShow = initializationFailureText;
    64.         else if(text == SubscriptionSceneText.connectToVerifyText)
    65.             textToShow = connectToVerifyText;
    66.        
    67.        
    68.         if(textToShow != null)
    69.         {
    70.             textToShow.SetActive(true);
    71.             TextMeshPro TMPro = textToShow.GetComponent<TextMeshPro>();
    72.             TextMeshProUGUI TMProUGUI = textToShow.GetComponent<TextMeshProUGUI>();
    73.             if(TMPro)
    74.             {
    75.                 Color c = TMPro.color;
    76.                 c.a = 1f;
    77.                 TMPro.color = c;
    78.                 TMPro.ForceMeshUpdate();
    79.             }
    80.             else if(TMProUGUI)
    81.             {
    82.                 Color c = TMProUGUI.color;
    83.                 c.a = 1f;
    84.                 TMProUGUI.color = c;
    85.                 TMProUGUI.ForceMeshUpdate();
    86.             }
    87.            
    88.         }
    89.         else
    90.         {
    91.             Debug.Log("There is no text gameObject assigned to this value!");
    92.         }
    93.  
    94.         return textToShow;
    95.     }
     
  4. JeffDUnity3D

    JeffDUnity3D

    Unity Technologies

    Joined:
    May 2, 2017
    Posts:
    838
    I have reviewed the issue, and your code. I don't believe this is a Unity IAP issue. I was looking for UI code in your purchase script as the error included "UnityRepaint", and I see that you look to be hiding/showing UI elements. The bug is likely in that portion of the code.
     
  5. arandono

    arandono

    Joined:
    Dec 4, 2014
    Posts:
    22
    As I said, I have tested this extensively in both TestFlight and Sandbox and I have actively monitored the app in XCode while it is running. There are no errors or warnings.
     
  6. arandono

    arandono

    Joined:
    Dec 4, 2014
    Posts:
    22
    Here's the code for showing/hiding buttons. As you can see it's pretty straightforward. Do you see anywhere where this code could go wrong?
    Code (CSharp):
    1.     public void activateSubscriptionButtons(bool turnOn)
    2.     {
    3.         if(SceneManager.GetActiveScene() != SceneManager.GetSceneByName("Subscription Purchase Scene"))
    4.         {
    5.             Debug.Log("Can't activate subscription buttons outside of Subscription Purchase Scene");
    6.             return;
    7.         }
    8.  
    9.         if(subscribeButton && subscribeButton.activeSelf == true)
    10.         {
    11.             Button button = subscribeButton.GetComponent<Button>();
    12.             if(button) button.enabled = turnOn;
    13.         }
    14.  
    15.         if(restoreButton && restoreButton.activeSelf == true)
    16.         {
    17.             Button button = restoreButton.GetComponent<Button>();
    18.             if(button) button.enabled = turnOn;
    19.         }
    20.  
    21.         if(yesButton && yesButton.activeSelf == true)
    22.         {
    23.             Button button = yesButton.GetComponent<Button>();
    24.             if(button) button.enabled = turnOn;
    25.         }
    26.  
    27.         if(noButton && noButton.activeSelf == true)
    28.         {
    29.             Button button = noButton.GetComponent<Button>();
    30.             if(button) button.enabled = turnOn;
    31.         }
    32.     }
    33.  
    34.  
    35.  
    36.  
    37.     public void showYesNoButtons(bool turnOn)
    38.     {
    39.         if(SceneManager.GetActiveScene() != SceneManager.GetSceneByName("Subscription Purchase Scene"))
    40.         {
    41.             Debug.Log("Can't activate subscription buttons outside of Subscription Purchase Scene");
    42.             return;
    43.         }
    44.  
    45.         activateSubscriptionButtons(true);
    46.         if(subscribeButton) subscribeButton.SetActive(!turnOn);
    47.         if(restoreButton)   restoreButton.SetActive(!turnOn);
    48.         if(yesButton)         yesButton.SetActive(turnOn);
    49.         if(noButton)         noButton.SetActive(turnOn);
    50.         activateSubscriptionButtons(true);
    51.     }
     
  7. JeffDUnity3D

    JeffDUnity3D

    Unity Technologies

    Joined:
    May 2, 2017
    Posts:
    838
    The error looks to be within the UI, and IAP does not do any UI. I would encourage you to capture the console output at runtime in XCode with your released app.