Just received a notification from Google that they disabled the payment method in my game because it doesn’t integrate pending transactions properly.
Here’s the message I got:
We’ve turned off some payment methods
Apr 23, 2022 03:06
We’ve noticed that you don’t always acknowledge the delivery of in-app content to users after they’ve paid outside your app. Or you don’t integrate pending transactions correctly. As a result, payment methods that require pending transactions, like paying with cash, have been turned off. Other payment methods aren’t affected. Contact us once you’ve fixed the issue so we can turn these payment methods back on.
I just imported Unity IAP and follow the codes in the documentation. Here’s my code:
using UnityEngine;
using UnityEngine.Purchasing;
using UnityEngine.Purchasing.Security;
public delegate void PurchaseSuccessCallback(string data);
public delegate void PurchaseFailureCallback(string product, string reason);
public class IAPManager : MonoBehaviour, IStoreListener {
private static IAPManager m_Instance;
public static IAPManager Instance => m_Instance;
// The Unity Purchasing system
private static IStoreController m_StoreController;
// The store-specific Purchasing subsystems
private static IExtensionProvider m_StoreExtensionProvider;
private static PurchaseSuccessCallback m_SuccessCallback;
private static PurchaseFailureCallback m_FailureCallback;
private static bool m_PreviousPurchaseInProgress = false;
private static string m_PurchasingProduct;
public static bool Initialized => m_StoreController != null && m_StoreExtensionProvider != null;
public static void Purchase(string product, PurchaseSuccessCallback onSuccess, PurchaseFailureCallback onFailed) {
m_PurchasingProduct = product;
if (!Initialized) {
Debug.Log("Failed to buy product. Not initialized.");
onFailed(product, PurchaseFailureReason.PurchasingUnavailable.ToString());
}
else if (m_PreviousPurchaseInProgress) {
Debug.Log("Failed to buy product. Previous Purchase in progress.");
onFailed(product, PurchaseFailureReason.PurchasingUnavailable.ToString());
}
else {
m_PreviousPurchaseInProgress = true;
m_SuccessCallback = onSuccess;
m_FailureCallback = onFailed;
Product storeProduct = m_StoreController.products.WithID(product);
if (storeProduct != null && storeProduct.availableToPurchase) {
Debug.Log("Purchasing product async: " + storeProduct.definition.id);
m_StoreController.InitiatePurchase(storeProduct);
}
else {
Debug.Log("Failed to buy product.");
}
}
}
void Awake() {
if (m_Instance) {
Destroy(gameObject);
return;
}
m_Instance = this;
DontDestroyOnLoad(this);
if (m_StoreController == null) {
InitializePurchasing();
}
}
public void OnInitialized(IStoreController controller, IExtensionProvider extensions) {
Debug.Log("IAP Initialized.");
m_StoreController = controller;
m_StoreExtensionProvider = extensions;
foreach (var product in m_StoreController.products.all) {
m_StoreController.ConfirmPendingPurchase(product);
}
}
public void OnInitializeFailed(InitializationFailureReason error) {
Debug.Log("Failed to initialize IAP: " + error);
}
public void OnPurchaseFailed(Product product, PurchaseFailureReason failureReason) {
m_PreviousPurchaseInProgress = false;
Debug.Log("Failed to purchase product: " + failureReason);
m_FailureCallback(m_PurchasingProduct, failureReason.ToString());
m_FailureCallback = null;
m_PurchasingProduct = null;
}
public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args) {
m_PreviousPurchaseInProgress = false;
string productId = args.purchasedProduct.definition.id;
// Receipt Validation
var validator = new CrossPlatformValidator(GooglePlayTangle.Data(), AppleTangle.Data(), Application.identifier);
try {
IPurchaseReceipt[] result = validator.Validate(args.purchasedProduct.receipt);
Debug.Log("Receipt is valid. Contents:");
foreach (IPurchaseReceipt productReceipt in result) {
Debug.Log(productReceipt.productID);
Debug.Log(productReceipt.purchaseDate);
Debug.Log(productReceipt.transactionID);
}
PlayerData savedData = PlayerDataManager.SavedData;
if (productId == "SmallCashPack") {
savedData.cash += 1000;
}
...
PlayerDataManager.Instance.Save();
}
catch (IAPSecurityException) {
Debug.Log("Invalid receipt, not unlocking content.");
}
if (m_SuccessCallback != null) {
m_SuccessCallback(productId);
m_SuccessCallback = null;
}
m_PurchasingProduct = IAPProduct.None;
return PurchaseProcessingResult.Complete;
}
public void InitializePurchasing() {
if (Initialized) {
return;
}
var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());
builder.AddProduct(GetProductId(IAPProduct.SmallCashPack), ProductType.Consumable);
...
UnityPurchasing.Initialize(this, builder);
}
}
Now all user payments are blocked and no more in-app purchases are available. It’s causing a serious impact on my company’s sales. What am I missing in my in-app purchase logic? How do I integrate pending transactions properly?