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?
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.
I’ve modified the actions to my needs. Here’s my actual code:
// Restore purchases previously made by this customer. Some platforms automatically restore purchases, like Google.
// Apple currently requires explicit purchase restoration for IAP, conditionally displaying a password prompt.
public void RestorePurchases()
{
subscriptionSceneControls SSC = subscriptionSceneControls.instance;
if(SSC) SSC.activateSubscriptionButtons(false);
// If Purchasing has not yet been set up ...
if (!IsInitialized())
{
// ... report the situation and stop restoring. Consider either waiting longer, or retrying initialization.
if(SSC) SSC.showText(SubscriptionSceneText.purchaseFailed);
if(SSC) SSC.showYesNoButtons(false);
if(SSC) SSC.activateSubscriptionButtons(true);
Debug.Log("RestorePurchases FAIL. Not initialized.");
return;
}
// If we are running on an Apple device ...
if (Application.platform == RuntimePlatform.IPhonePlayer || Application.platform == RuntimePlatform.OSXPlayer)
{
// ... begin restoring purchases. Display message to user here that we are fetching their receipt.
Debug.Log("RestorePurchases started ...");
if(SSC) SSC.showText(SubscriptionSceneText.restoringPurchases);
// Fetch the Apple store-specific subsystem.
var apple = m_StoreExtensionProvider.GetExtension<IAppleExtensions>();
// Begin the asynchronous process of restoring purchases. Expect a confirmation response in
// the Action<bool> below, and ProcessPurchase if there are previously purchased products to restore.
apple.RestoreTransactions((result) => {
// The first phase of restoration. If no more responses are received on ProcessPurchase then
// no purchases are available to be restored.
Debug.Log("RestorePurchases continuing: " + result + ". If no further messages, no purchases available to restore.");
bool isActive = false;
if(result)
{
Debug.Log("RestorePurchases: Restoring Purchases. Result = " + result);
// Check the expiration date on the restored receipt to allow access to figure
if(IsInitialized())
{
Product product = m_StoreController.products.WithID(figureMonthlySubscription);
string receiptString = product.receipt;
CrossPlatformValidator validator = new CrossPlatformValidator(GooglePlayTangle.Data(), AppleTangle.Data(), Application.identifier);
IPurchaseReceipt[] receipts = validator.Validate(receiptString);
IPurchaseReceipt mostRecent = findMostRecentReceipt(receipts);
saveReceiptsToFile(product);
isActive = isSubscriptionActive(mostRecent);
}
}
else
{
if(SSC) SSC.showText(SubscriptionSceneText.purchaseFailed);
if(SSC) SSC.showYesNoButtons(false);
}
if(isActive == true)
{
allowAccessToFigure = true;
}
else if(isActive == false)
{
// Display pop-up message saying that the we don't have an activeSubscription on record
if(SSC) SSC.showText(SubscriptionSceneText.noReceipts);
if(SSC) SSC.showYesNoButtons(false);
}
//waiting = false;
});
}
// Otherwise ...
else
{
// We are not running on an Apple device. No work is necessary to restore purchases.
if(SSC) SSC.showText(SubscriptionSceneText.purchaseFailed);
if(SSC) SSC.showYesNoButtons(false);
Debug.Log("RestorePurchases FAIL. Not supported on this platform. Current = " + Application.platform);
//waiting = false;
}
if(SSC) SSC.activateSubscriptionButtons(true);
}
}
The custom code I’ve added to show text and buttons is pretty straightforward. Here’s the showText code:
// Use this along with the SubscriptionSceneText enum to show whatever text needs to be shown
public GameObject showText(SubscriptionSceneText text)
{
Debug.Log("showText called for " + text);
#if UNITY_ANDROID
if(text == SubscriptionSceneText.subscribe) // Just making sure that the subscribe button is active
{
subscriptionSceneControls.instance.subscribeButton.SetActive (true);
}
#endif
if(SceneManager.GetActiveScene() != SceneManager.GetSceneByName("Subscription Purchase Scene"))
{
Debug.Log("Can't show subscription scene text outside of Subscription Purchase Scene!");
return null;
}
if(subscribeText) subscribeText.SetActive(false);
if(purchaseInfoText) purchaseInfoText.SetActive(false);
if(connectToInternetText) connectToInternetText.SetActive(false);
if(noReceiptsText) noReceiptsText.SetActive(false);
if(grabbingReceiptText) grabbingReceiptText.SetActive(false);
if(communicatingWithStoreText) communicatingWithStoreText.SetActive(false);
if(restoringPurchasesText) restoringPurchasesText.SetActive(false);
if(purchaseFailedText) purchaseFailedText.SetActive(false);
if(paymentDeclinedText) paymentDeclinedText.SetActive(false);
if(promptRestoreText) promptRestoreText.SetActive(false);
if(productUnavailableText) productUnavailableText.SetActive(false);
if(goFigureText) goFigureText.SetActive(false);
if(initializationFailureText) initializationFailureText.SetActive(false);
if(connectToVerifyText) connectToVerifyText.SetActive(false);
GameObject textToShow = null;
if(text == SubscriptionSceneText.none)
textToShow = null;
else if(text == SubscriptionSceneText.purchaseInfo)
textToShow = purchaseInfoText;
else if(text == SubscriptionSceneText.subscribe)
textToShow = subscribeText;
else if(text == SubscriptionSceneText.connectToInternet)
textToShow = connectToInternetText;
else if(text == SubscriptionSceneText.noReceipts)
textToShow = noReceiptsText;
else if(text == SubscriptionSceneText.grabbingReceipt)
textToShow = grabbingReceiptText;
else if(text == SubscriptionSceneText.communicatingWithStore)
textToShow = communicatingWithStoreText;
else if(text == SubscriptionSceneText.restoringPurchases)
textToShow = restoringPurchasesText;
else if(text == SubscriptionSceneText.purchaseFailed)
textToShow = purchaseFailedText;
else if(text == SubscriptionSceneText.paymentDeclined)
textToShow = paymentDeclinedText;
else if(text == SubscriptionSceneText.promptRestore)
textToShow = promptRestoreText;
else if(text == SubscriptionSceneText.productUnavailable)
textToShow = productUnavailableText;
else if(text == SubscriptionSceneText.goFigure)
textToShow = goFigureText;
else if(text == SubscriptionSceneText.initializationFailure)
textToShow = initializationFailureText;
else if(text == SubscriptionSceneText.connectToVerifyText)
textToShow = connectToVerifyText;
if(textToShow != null)
{
textToShow.SetActive(true);
TextMeshPro TMPro = textToShow.GetComponent<TextMeshPro>();
TextMeshProUGUI TMProUGUI = textToShow.GetComponent<TextMeshProUGUI>();
if(TMPro)
{
Color c = TMPro.color;
c.a = 1f;
TMPro.color = c;
TMPro.ForceMeshUpdate();
}
else if(TMProUGUI)
{
Color c = TMProUGUI.color;
c.a = 1f;
TMProUGUI.color = c;
TMProUGUI.ForceMeshUpdate();
}
}
else
{
Debug.Log("There is no text gameObject assigned to this value!");
}
return textToShow;
}
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.
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.
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.
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] : 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] : 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] : UnityIAP:restoreTransactions
Jan 24 15:15:24 Andrews-iPad Figure[926] : UnityIAP:RestorePurchase
Jan 24 15:15:24 Andrews-iPad itunesstored(BiometricKit)[130] : identities: 0x0((null))
Jan 24 15:15:24 Andrews-iPad biometrickitd(libBKDM1.dylib)[127] : identities:withClient: 0x0((null))
Jan 24 15:15:24 Andrews-iPad biometrickitd(libBKDM1.dylib)[127] : identities:withClient: →
Jan 24 15:15:24 Andrews-iPad itunesstored(BiometricKit)[130] : identities: →
Jan 24 15:15:24 Andrews-iPad itunesstored(iTunesStore)[130] : ISBiometricStore: Attached biometric state to request: D
Jan 24 15:15:24 Andrews-iPad itunesstored(iTunesStore)[130] : ISStoreURLOperation: Sending headers for :
Jan 24 15:15:24 Andrews-iPad itunesstored(CFNetwork)[130] : Task <42ED64B9-4530-43DC-B7D4-85689640F71B>.<1039> now using Connection 372
Jan 24 15:15:24 Andrews-iPad itunesstored(CFNetwork)[130] : Task <42ED64B9-4530-43DC-B7D4-85689640F71B>.<1039> sent request, body S
Jan 24 15:15:25 Andrews-iPad itunesstored(CFNetwork)[130] : Task <42ED64B9-4530-43DC-B7D4-85689640F71B>.<1039> received response, status 200 content K
Jan 24 15:15:25 Andrews-iPad itunesstored(CFNetwork)[130] : Task <42ED64B9-4530-43DC-B7D4-85689640F71B>.<1039> response ended
Jan 24 15:15:25 Andrews-iPad itunesstored(iTunesStore)[130] : ISStoreURLOperation: Received headers for :
Jan 24 15:15:25 Andrews-iPad Figure[926] : UnityIAP:PaymentQueueRestoreCompletedTransactionsFinished
Jan 24 15:15:25 Andrews-iPad Figure[926] : 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] : 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] : 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] : 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] : Task .<0> now using Connection 1
Jan 24 15:15:25 Andrews-iPad Figure(CFNetwork)[926] : Task .<0> sent request, body S
Jan 24 15:15:25 Andrews-iPad SpringBoard(KeyboardArbiter)[55] : HW kbd: Failed to set (null) as keyboard focus
Jan 24 15:15:25 Andrews-iPad assertiond[66] : 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] : [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] : [Figure:926] dump all assertions HWM:3 (deactivateAssertion): {
<BKProcessAssertion: 0x10533bcd0; “Resume” (activation:inf); id:\M-b\M^@\M-&7864C8B9C59B> [active]
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 Unity Cloud so that we can assist you more directly?