I recently encountered two cases where users purchased in-app products (IAPs) via the Google Play Store, but our game crashed during the payment process. Although the payments were successful, the crashes caused issues. Has anyone entered the same problem?
Unity version: 2022.3.39f1
IAP version: 4.12.2
Case 1:
- Device Model: Xiaomi Mi 10T 5G
Case 2:
- Device Model: Samsung Galaxy S22 Ultra
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Purchasing;
using UnityEngine.Purchasing.Extension;
public class IapInfo
{
public int shopID;
public string productID;
public bool isResource;
public IapInfo(int shopID, string productID, bool isResource)
{
this.shopID = shopID;
this.productID = productID;
this.isResource = isResource;
}
}
public class IapManager : MonoBehaviour, IDetailedStoreListener
{
public static IapManager instance { get; private set; }
private static IStoreController storeController;
private static IExtensionProvider storeExtension;
private IAppleExtensions appleExtensions;
private IGooglePlayStoreExtensions googleExtensions;
private ConfigurationBuilder builder;
private bool isReady = false;
private List<IapInfo> listIapInfo;
private Product pendingProduct;
private string lastOrderTransactionID;
private void Awake()
{
instance = this;
DontDestroyOnLoad(this);
listIapInfo = new List<IapInfo>();
}
public void Init(List<BundleShop> listBundle, List<ResourceShop> listResource, List<PassData> listPassData, List<ShopPromotionData> listShopPromotionData)
{
builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());
for (int i = 0; i < listBundle.Count; i++)
{
if (!string.IsNullOrEmpty(listBundle[i].iapKey))
{
builder.AddProduct(listBundle[i].iapKey, ProductType.Consumable);
listIapInfo.Add(new IapInfo(listBundle[i].shopId, listBundle[i].iapKey, false));
}
}
for (int i = 0; i < listResource.Count; i++)
{
if (!string.IsNullOrEmpty(listResource[i].iapKey))
{
builder.AddProduct(listResource[i].iapKey, ProductType.Consumable);
listIapInfo.Add(new IapInfo(listResource[i].shopId, listResource[i].iapKey, true));
}
}
for (int i = 0; i < listPassData.Count; i++)
{
if (!string.IsNullOrEmpty(listPassData[i].iapKey))
{
builder.AddProduct(listPassData[i].iapKey, ProductType.Consumable);
listIapInfo.Add(new IapInfo(listPassData[i].passID, listPassData[i].iapKey, true));
}
}
if (listShopPromotionData.Count <= 0)
{
string config = Database.instance.GetGlobalValueByKey("PROMOTION_DB");
if (config != null)
{
listShopPromotionData = Utility.ReadObjectList<ShopPromotionData>(config);
}
}
for (int i = 0; i < listShopPromotionData.Count; i++)
{
if (!string.IsNullOrEmpty(listShopPromotionData[i].iapKey))
{
builder.AddProduct(listShopPromotionData[i].iapKey, ProductType.Consumable);
listIapInfo.Add(new IapInfo(listShopPromotionData[i].promotionID, listShopPromotionData[i].iapKey, true));
}
}
//Dialog.instance.ShowDialog("IAP: INIT");
Packet.instance.SendLogsIapStatus("IAP: start INIT");
Debug.Log("IAP: start INIT");
UnityPurchasing.Initialize(this, builder);
}
public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
{
//Dialog.instance.ShowDialog("IAP: OnInitialized");
storeController = controller;
storeExtension = extensions;
appleExtensions = extensions.GetExtension<IAppleExtensions>();
googleExtensions = extensions.GetExtension<IGooglePlayStoreExtensions>();
isReady = true;
Packet.instance.SendLogsIapStatus("OnInitialized");
Debug.Log("IAP: OnInitialized");
}
public void OnInitializeFailed(InitializationFailureReason error, string message)
{
#if UNITY_ANDROID
PopupMessage.instance.ShowPopupMessage(Localization.GetValue(Consts.DIALOG_IAP_ANDROID_INIT_FAILED) + "<br>Error: " + error + " => " + message, Localization.GetValue(Consts.POPUP_WARNING_TITLE));
#endif
#if UNITY_IOS
PopupMessage.instance.ShowPopupMessage(Localization.GetValue(Consts.DIALOG_IAP_IOS_INIT_FAILED) + "<br>Error: " + error + " => " + message, Localization.GetValue(Consts.POPUP_WARNING_TITLE));
#endif
Packet.instance.SendLogsIapStatus("OnInitializeFailed", $"message : {error}: {message}");
}
public void OnInitializeFailed(InitializationFailureReason error)
{
#if UNITY_ANDROID
PopupMessage.instance.ShowPopupMessage(Localization.GetValue(Consts.DIALOG_IAP_ANDROID_INIT_FAILED) + "<br>Error: => " + error, Localization.GetValue(Consts.POPUP_WARNING_TITLE));
#endif
#if UNITY_IOS
PopupMessage.instance.ShowPopupMessage(Localization.GetValue(Consts.DIALOG_IAP_IOS_INIT_FAILED) + "<br>Error: => " + error, Localization.GetValue(Consts.POPUP_WARNING_TITLE));
#endif
Packet.instance.SendLogsIapStatus($"OnInitializeFailed: {error} :", "no message");
}
public void BuyProductID(string productID, string orderTransactionID)
{
Packet.instance.SendLogsIapStatus("BuyProductID: will Action => ");
if (isReady)
{
Packet.instance.SendLogsIapStatus("BuyProductID: Ready => " + isReady, productID, orderTransactionID);
lastOrderTransactionID = orderTransactionID;
pendingProduct = storeController.products.WithID(productID);
if (pendingProduct != null && pendingProduct.availableToPurchase)
{
Debug.Log($"BuyProductID: InitiatePurchase {productID} {orderTransactionID}");
Packet.instance.SendLogsIapStatus($"BuyProductID: InitiatePurchase {pendingProduct.definition.id} : {pendingProduct.definition.storeSpecificId} : {pendingProduct.definition.enabled}", productID, orderTransactionID);
//Dialog.instance.ShowDialog("Initiate Purchase : " + pendingProduct.definition.id);
GameManager.instance.isOnIapProcess = true;
try
{
storeController.InitiatePurchase(pendingProduct);
}
catch (Exception exception)
{
Debug.LogException(exception);
Packet.instance.SendLogsIapStatus($"BuyProductID: InitiatePurchase Error {exception.Message}");
}
}
else
{
Packet.instance.SendLogsIapStatus("BuyProductID: failed no availableToPurchase", productID, orderTransactionID);
Packet.instance.SendShopIAPFail(GameManager.instance.ownerUser.userBase.userID, "", lastOrderTransactionID, PurchaseFailureReason.ProductUnavailable.ToString());
#if UNITY_ANDROID
PopupMessage.instance.ShowPopupMessage(string.Format(Localization.GetValue(Consts.DIALOG_PURCHASE_FAIL_WITH_DETAIL),
Localization.GetValue(Consts.DIALOG_IAP_ANDROID_NO_PRODUCT)),
Localization.GetValue(Consts.DIALOG_PURCHASE_FAIL_HEADER)); // not found or not available
GameManager.instance.isOnIapProcess = false;
LoadingPopup.instance.HideLoading();
#endif
#if UNITY_IOS
PopupMessage.instance.ShowPopupMessage(string.Format(Localization.GetValue(Consts.DIALOG_PURCHASE_FAIL_WITH_DETAIL),
Localization.GetValue(Consts.DIALOG_IAP_IOS_NO_PRODUCT)),
Localization.GetValue(Consts.DIALOG_PURCHASE_FAIL_HEADER)); // not found or not available
GameManager.instance.isOnIapProcess = false;
LoadingPopup.instance.HideLoading();
#endif
}
}
else
{
Packet.instance.SendLogsIapStatus("BuyProductID: failed iap not ready", productID, orderTransactionID);
Packet.instance.SendShopIAPFail(GameManager.instance.ownerUser.userBase.userID, "", lastOrderTransactionID, PurchaseFailureReason.PurchasingUnavailable.ToString());
#if UNITY_ANDROID
PopupMessage.instance.ShowPopupMessage(string.Format(Localization.GetValue(Consts.DIALOG_PURCHASE_FAIL_WITH_DETAIL),
Localization.GetValue(Consts.DIALOG_IAP_ANDROID_NOT_INIT)),
Localization.GetValue(Consts.DIALOG_PURCHASE_FAIL_HEADER)); // iap not init
GameManager.instance.isOnIapProcess = false;
LoadingPopup.instance.HideLoading();
#endif
#if UNITY_IOS
PopupMessage.instance.ShowPopupMessage(string.Format(Localization.GetValue(Consts.DIALOG_PURCHASE_FAIL_WITH_DETAIL),
Localization.GetValue(Consts.DIALOG_IAP_IOS_NOT_INIT)),
Localization.GetValue(Consts.DIALOG_PURCHASE_FAIL_HEADER)); // iap not init
GameManager.instance.isOnIapProcess = false;
LoadingPopup.instance.HideLoading();
#endif
}
}
public void PurchaseComplete()
{
GameManager.instance.isOnIapProcess = false;
LoadingPopup.instance.HideLoading();
if (pendingProduct == null)
{
Dialog.instance.ShowDialog("Error multiple purchases");
return;
}
if (pendingProduct.definition.id.Equals("bundle_00004") || pendingProduct.definition.id.Equals("bundle_00005")
|| pendingProduct.definition.id.Equals("bundle_00006"))
{
if (FireBaseAnalytic.instance.abTestType == ABTesttype.BANNER)
{
FireBaseAnalytic.instance.TrackerPurchaseSuccess(Consts.AB01_PURCHASE_BANNER_SUCCESS, int.Parse(pendingProduct.definition.id));
}
else if (FireBaseAnalytic.instance.abTestType == ABTesttype.ICON)
{
FireBaseAnalytic.instance.TrackerPurchaseSuccess(Consts.AB01_PURCHASE_ICON_SUCCESS, int.Parse(pendingProduct.definition.id));
}
}
FireBaseAnalytic.instance.abTestType = ABTesttype.NONE;
Dialog.instance.ShowDialog("PurchaseComplete");
storeController.ConfirmPendingPurchase(pendingProduct);
LoadingPopup.instance.HideLoading();
Packet.instance.SendLogsIapStatus("PurchaseComplete");
}
public void OnPurchaseFailed(Product product, PurchaseFailureDescription failureDescription)
{
LoadingPopup.instance.HideLoading();
GameManager.instance.isOnIapProcess = false;
if (product.definition.id.Equals("bundle_00004") || product.definition.id.Equals("bundle_00005")
|| product.definition.id.Equals("bundle_00006"))
{
if (FireBaseAnalytic.instance.abTestType == ABTesttype.BANNER)
{
FireBaseAnalytic.instance.TrackerPurchaseFail(Consts.AB01_PURCHASE_BANNER_FAIL, int.Parse(product.definition.id));
}
else if (FireBaseAnalytic.instance.abTestType == ABTesttype.ICON)
{
FireBaseAnalytic.instance.TrackerPurchaseFail(Consts.AB01_PURCHASE_ICON_FAIL, int.Parse(product.definition.id));
}
}
if (product != null)
{
Packet.instance.SendShopIAPFail(GameManager.instance.ownerUser.userBase.userID, product.transactionID, lastOrderTransactionID, failureDescription.ToString());
}
FireBaseAnalytic.instance.abTestType = ABTesttype.NONE;
Packet.instance.SendLogsIapStatus($"OnPurchaseFailed: id:{product.definition.id} transactionID: {product.transactionID}", $"failureDescription: {failureDescription.reason} / {failureDescription.message}");
PopupMessage.instance.ShowPopupMessage(string.Format(Localization.GetValue(Consts.DIALOG_PURCHASE_FAIL_WITH_DETAIL), failureDescription.ToString()), Localization.GetValue(Consts.DIALOG_PURCHASE_FAIL_HEADER));
}
public void OnPurchaseFailed(Product product, PurchaseFailureReason failureReason)
{
GameManager.instance.isOnIapProcess = false;
LoadingPopup.instance.HideLoading();
IapInfo info = null;
Packet.instance.SendLogsIapStatus($"OnPurchaseFailed: {failureReason} <= ", product.definition.id, product.transactionID);
for (int i = 0; i < listIapInfo.Count; i++)
{
if (listIapInfo[i].productID == product.definition.id)
{
info = listIapInfo[i];
break;
}
}
if (info != null)
{
Packet.instance.SendShopIAPFail(GameManager.instance.ownerUser.userBase.userID, product.transactionID, lastOrderTransactionID, failureReason.ToString());
}
if (product.definition.id.Equals("bundle_00004") || product.definition.id.Equals("bundle_00005")
|| product.definition.id.Equals("bundle_00006"))
{
if (FireBaseAnalytic.instance.abTestType == ABTesttype.BANNER)
{
FireBaseAnalytic.instance.TrackerPurchaseFail(Consts.AB01_PURCHASE_BANNER_FAIL, int.Parse(product.definition.id));
}
else if (FireBaseAnalytic.instance.abTestType == ABTesttype.ICON)
{
FireBaseAnalytic.instance.TrackerPurchaseFail(Consts.AB01_PURCHASE_ICON_FAIL, int.Parse(product.definition.id));
}
}
FireBaseAnalytic.instance.abTestType = ABTesttype.NONE;
PopupMessage.instance.ShowPopupMessage(string.Format(Localization.GetValue(Consts.DIALOG_PURCHASE_FAIL_WITH_DETAIL), failureReason), Localization.GetValue(Consts.DIALOG_PURCHASE_FAIL_HEADER));
LoadingPopup.instance.HideLoading();
}
public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs purchaseEvent)
{
//PopupMessage.instance.ShowPopupMessage("iap success " + purchaseEvent.purchasedProduct.definition.id + ", " + purchaseEvent.purchasedProduct.transactionID, "iap success");
Packet.instance.SendLogsIapStatus("iap success ProcessPurchase: ", purchaseEvent.purchasedProduct.definition.id, purchaseEvent.purchasedProduct.transactionID);
IapInfo info = null ;
for (int i = 0; i < listIapInfo.Count; i++)
{
if (listIapInfo[i].productID == purchaseEvent.purchasedProduct.definition.id)
{
info = listIapInfo[i];
break;
}
}
if (info != null)
{
Packet.instance.SendLogsIapStatus("SHOP_IAP_SUCCESS BuyProductID: Purchase", info.productID, lastOrderTransactionID);
GameManager.instance.isOnIapProcess = false;
LoadingPopup.instance.ShowLoading("CS_SHOP_IAP_SUCCESS");
Packet.instance.SendShopIAPSuccess(GameManager.instance.ownerUser.userBase.userID, purchaseEvent.purchasedProduct.transactionID, lastOrderTransactionID);
}
return PurchaseProcessingResult.Pending;
}
}