Search Unity

[Closed] IAP Restore Purchases bug in Live version

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

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

    acr1378

    Joined:
    Dec 4, 2014
    Posts:
    76
    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:
    1,519
  3. acr1378

    acr1378

    Joined:
    Dec 4, 2014
    Posts:
    76
    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

    Joined:
    May 2, 2017
    Posts:
    14,446
    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. acr1378

    acr1378

    Joined:
    Dec 4, 2014
    Posts:
    76
    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. acr1378

    acr1378

    Joined:
    Dec 4, 2014
    Posts:
    76
    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

    Joined:
    May 2, 2017
    Posts:
    14,446
    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.
     
  8. acr1378

    acr1378

    Joined:
    Dec 4, 2014
    Posts:
    76
    I was finally able to get console logs at runtime through XCode. From what I can see, the issue is not a UI issue -- it looks like an IAP bug. It seems to start with an InvalidReceiptDataException from UnityEngine.Purchasing.Security.

    Can you help me out here. I REALLY need to get this resolved.




    (Filename: /Users/builduser/buildslave/unity/build/artifacts/generated/common/runtime/DebugBindings.gen.cpp Line: 51)
    Jan 24 15:15:24 Andrews-iPad Figure[926] <Notice>: RestorePurchases started ...

    (Filename: /Users/builduser/buildslave/unity/build/artifacts/generated/common/runtime/DebugBindings.gen.cpp Line: 51)
    Jan 24 15:15:24 Andrews-iPad Figure[926] <Notice>: showText called for restoringPurchases

    (Filename: /Users/builduser/buildslave/unity/build/artifacts/generated/common/runtime/DebugBindings.gen.cpp Line: 51)
    Jan 24 15:15:24 Andrews-iPad Figure[926] <Notice>: UnityIAP:restoreTransactions
    Jan 24 15:15:24 Andrews-iPad Figure[926] <Notice>: UnityIAP:RestorePurchase
    Jan 24 15:15:24 Andrews-iPad itunesstored(BiometricKit)[130] <Notice>: identities: 0x0((null))
    Jan 24 15:15:24 Andrews-iPad biometrickitd(libBKDM1.dylib)[127] <Notice>: identities:withClient: 0x0((null)) <private>
    Jan 24 15:15:24 Andrews-iPad biometrickitd(libBKDM1.dylib)[127] <Notice>: identities:withClient: -> <private>
    Jan 24 15:15:24 Andrews-iPad itunesstored(BiometricKit)[130] <Notice>: identities: -> <private>
    Jan 24 15:15:24 Andrews-iPad itunesstored(iTunesStore)[130] <Notice>: ISBiometricStore: Attached biometric state to request: D
    Jan 24 15:15:24 Andrews-iPad itunesstored(iTunesStore)[130] <Notice>: ISStoreURLOperation: Sending headers for <private>:
    <private>
    Jan 24 15:15:24 Andrews-iPad itunesstored(CFNetwork)[130] <Notice>: Task <42ED64B9-4530-43DC-B7D4-85689640F71B>.<1039> now using Connection 372
    Jan 24 15:15:24 Andrews-iPad itunesstored(CFNetwork)[130] <Notice>: Task <42ED64B9-4530-43DC-B7D4-85689640F71B>.<1039> sent request, body S
    Jan 24 15:15:25 Andrews-iPad itunesstored(CFNetwork)[130] <Notice>: Task <42ED64B9-4530-43DC-B7D4-85689640F71B>.<1039> received response, status 200 content K
    Jan 24 15:15:25 Andrews-iPad itunesstored(CFNetwork)[130] <Notice>: Task <42ED64B9-4530-43DC-B7D4-85689640F71B>.<1039> response ended
    Jan 24 15:15:25 Andrews-iPad itunesstored(iTunesStore)[130] <Notice>: ISStoreURLOperation: Received headers for <private>:
    <private>
    Jan 24 15:15:25 Andrews-iPad Figure[926] <Notice>: UnityIAP:paymentQueueRestoreCompletedTransactionsFinished
    Jan 24 15:15:25 Andrews-iPad Figure[926] <Notice>: RestorePurchases continuing: True. If no further messages, no purchases available to restore.

    (Filename: /Users/builduser/buildslave/unity/build/artifacts/generated/common/runtime/DebugBindings.gen.cpp Line: 51)
    Jan 24 15:15:25 Andrews-iPad Figure[926] <Notice>: RestorePurchases: Restoring Purchases. Result = True

    (Filename: /Users/builduser/buildslave/unity/build/artifacts/generated/common/runtime/DebugBindings.gen.cpp Line: 51)
    Jan 24 15:15:25 Andrews-iPad Figure[926] <Notice>: InvalidReceiptDataException: Exception of type 'UnityEngine.Purchasing.Security.InvalidReceiptDataException' was thrown.


    (Filename: currently not available on il2cpp Line: -1)
    Jan 24 15:15:25 Andrews-iPad Figure[926] <Notice>: InvalidReceiptDataException: Exception of type 'UnityEngine.Purchasing.Security.InvalidReceiptDataException' was thrown.


    (Filename: currently not available on il2cpp Line: -1)
    Jan 24 15:15:25 Andrews-iPad Figure(CFNetwork)[926] <Notice>: Task <B831294C-2CDC-4607-89E2-4F08937E7C4F>.<0> now using Connection 1
    Jan 24 15:15:25 Andrews-iPad Figure(CFNetwork)[926] <Notice>: Task <B831294C-2CDC-4607-89E2-4F08937E7C4F>.<0> sent request, body S
    Jan 24 15:15:25 Andrews-iPad SpringBoard(KeyboardArbiter)[55] <Error>: HW kbd: Failed to set (null) as keyboard focus
    Jan 24 15:15:25 Andrews-iPad assertiond[66] <Notice>: Client relinquished <BKProcessAssertion: 0x10524cd60; "CMSession.926."com.AlefOmega.Figure"."AmbientSound".isPlayingProcessAssertion" (audio:inf); id:\M-b\M^@\M-&A8E6F8E0F240>
    Jan 24 15:15:25 Andrews-iPad assertiond[66] <Notice>: [Figure:926] Deactivate assertion: <BKProcessAssertion: 0x10524cd60; "CMSession.926."com.AlefOmega.Figure"."AmbientSound".isPlayingProcessAssertion" (audio:inf); id:\M-b\M^@\M-&A8E6F8E0F240>
    Jan 24 15:15:25 Andrews-iPad assertiond[66] <Notice>: [Figure:926] dump all assertions HWM:3 (deactivateAssertion): {
    <BKProcessAssertion: 0x10533bcd0; "Resume" (activation:inf); id:\M-b\M^@\M-&7864C8B9C59B> [active]
     
  9. JeffDUnity3D

    JeffDUnity3D

    Joined:
    May 2, 2017
    Posts:
    14,446
    Thank you for the additional information. I will check with the IAP team here. In the meantime, would you be able to open a support ticket at https://analytics.cloud.unity3d.com/support so that we can assist you more directly?
     
  10. acr1378

    acr1378

    Joined:
    Dec 4, 2014
    Posts:
    76
    Ok, I opened a support ticket. I did submit a bug report at least two weeks ago, but never got a response back.
     
Thread Status:
Not open for further replies.