Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

(SOLVED) Purchase is refunded after 3 days because it was not acknoledged

Discussion in 'Unity IAP' started by Kennai, May 9, 2021.

  1. Kennai

    Kennai

    Joined:
    Nov 1, 2018
    Posts:
    27
    Greetings everyone!

    What I have:
    Unity 2019.2.8f1
    Platform: Android
    IAP (Package Manager) 2.2.2
    In-app purchasing (service, not asset) 2.2.7

    My purchases worked fine until this year. This year I got notice from google about to upgrade to Billing library v3. So I decided to upgrade unity IAP libraries to latest. (Before that I upgraded to latest IAP at summer 2020).

    After update I didnt worried about purchases, because they worked before and I was sure it was working fine. But I was wrong. During few months I got few purchases and all of them was refunded after 3 days (automatically). Google Tech said that I have to acknoledge purchase.
    I re-checked my code and found that in in ProcessPurchase I return Pending status, it was my mistake, I changed it to Complete status, but problem still be the same.

    I started to check billing system with test payment. And here is what I have:
    1. When I make purchase, google says that I bough it.
    2. After it, when I return to app, I got error with OnPurchaseFailed with reason Unknown and Store specific error code also Unknown.
    3. after 1 min my test purchase is refunded and I got email from google that Purchase is refunded and I have to acknoledge purchase within 3 days.

    I tried to check IAPDemo.cs and my InAppManager.cs to find what I miss, but I cant find anything.
    Maybe, guys, you can help me to point out what I missed?

    I used my custom logging into *.log file, but can try to send default log info also, if needed.
    But I am not sure that IAP writes something by default, because I didnt find anything useful in logcat

    my store has next non-consumable items:
    10 items +1000 scores
    5 items +2000 scores
    2 items +5000 scores
    1 item +10000 scores

    Code (CSharp):
    1. public class IAPManager : MonoBehaviour, IStoreListener
    2. {
    3.     private static IAPManager instance = null;
    4.     private static IStoreController m_StoreController;
    5.     private static IExtensionProvider m_StoreExtensionProvider;
    6.  
    7.     public static int index1 = 0;
    8.     public static int index2 = 0;
    9.     public static int index5 = 0;
    10.     public static int index10 = 0;
    11.     public static int index20 = 0;
    12.     public static bool HasBonus { get; private set; } = false;
    13.  
    14.     public const string mask_points = "com.criongames.defensepoint.point_";
    15.     public const string mask_1 = "com.criongames.defensepoint.point_1_";
    16.     public const string mask_2 = "com.criongames.defensepoint.point_2_";
    17.     public const string mask_5 = "com.criongames.defensepoint.point_5_";
    18.     public const string mask_10 = "com.criongames.defensepoint.point_10_";
    19.  
    20.     private ITransactionHistoryExtensions m_TransactionHistoryExtensions;
    21.  
    22.     public static IAPManager GetInstance()
    23.     {
    24.         if (instance == null)
    25.             instance = GameObject.FindObjectOfType<IAPManager>();
    26.         return instance;
    27.     }
    28.  
    29.     private void Awake()
    30.     {
    31.         if (instance == null)
    32.             instance = this;
    33.         else if (instance != this)
    34.         {
    35.             Destroy(gameObject);
    36.             return;
    37.         }
    38.         DontDestroyOnLoad(gameObject);
    39.  
    40.         //my own points system
    41.         PointsSystem.Initialize();
    42.     }
    43.  
    44.     private void OnDestroy()
    45.     {
    46.         if (instance == this)
    47.             instance = null;
    48.     }
    49.  
    50.     void Start()
    51.     {
    52.         if (m_StoreController == null)
    53.         {
    54.             InitializePurchasing();
    55.         }
    56.     }
    57.  
    58.     public bool IsInitialized()
    59.     {
    60.         return m_StoreController != null && m_StoreExtensionProvider != null;
    61.     }
    62.  
    63.     /// <summary>
    64.     /// first IAP initialization
    65.     /// </summary>
    66.     public void InitializePurchasing()
    67.     {
    68.         if (IsInitialized())
    69.         {
    70.             return;
    71.         }
    72.  
    73.         var module = StandardPurchasingModule.Instance();
    74.         var builder = ConfigurationBuilder.Instance(module);
    75.  
    76.         //crion 1
    77.         for (int i = 0; i < 10; i++)
    78.             builder.AddProduct(mask_1 + i.ToString(), ProductType.NonConsumable);
    79.  
    80.         //crion 2
    81.         for (int i = 0; i < 5; i++)
    82.             builder.AddProduct(mask_2 + i.ToString(), ProductType.NonConsumable);
    83.  
    84.         //crion 5
    85.         for (int i = 0; i < 2; i++)
    86.             builder.AddProduct(mask_5 + i.ToString(), ProductType.NonConsumable);
    87.  
    88.         //crion 10
    89.         for (int i = 0; i < 1; i++)
    90.             builder.AddProduct(mask_10 + i.ToString(), ProductType.NonConsumable);
    91.  
    92.         UnityPurchasing.Initialize(this, builder);
    93.     }
    94.  
    95.     /// <summary>
    96.     /// when initialization is OK
    97.     /// </summary>
    98.     /// <param name="controller"></param>
    99.     /// <param name="extensions"></param>
    100.     public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
    101.     {
    102.         m_StoreController = controller;
    103.         m_StoreExtensionProvider = extensions;
    104.         m_TransactionHistoryExtensions = extensions.GetExtension<ITransactionHistoryExtensions>();
    105.  
    106.         if (!PointsSystem.PurchaseInitiated)
    107.         {
    108.             PointsSystem.ClearPoints();
    109.  
    110.             //restore purchased things after app restart
    111.             InitPurchase();
    112.         }
    113.  
    114.         //TEMP LOG
    115.         LogProductDefinitions();
    116.     }
    117.  
    118.     /// <summary>
    119.     /// Logging stuff
    120.     /// </summary>
    121.     private void LogProductDefinitions()
    122.     {
    123.         ZLog.Write("Available items:");
    124.         foreach (var item in m_StoreController.products.all)
    125.         {
    126.             if (item.availableToPurchase)
    127.             {
    128.                 ZLog.Write(string.Join(" - ",
    129.                     new[]
    130.                     {
    131.                         item.metadata.localizedTitle,
    132.                         item.metadata.localizedDescription,
    133.                         item.metadata.isoCurrencyCode,
    134.                         item.metadata.localizedPrice.ToString(),
    135.                         item.metadata.localizedPriceString,
    136.                         item.transactionID,
    137.                         item.receipt
    138.                     }));
    139.             }
    140.         }
    141.  
    142.  
    143.  
    144.         var products = m_StoreController.products.all;
    145.         foreach (var product in products)
    146.         {
    147. #if UNITY_5_6_OR_NEWER
    148.             ZLog.Write(string.Format("id: {0}\nstore-specific id: {1}\ntype: {2}\nenabled: {3}\n", product.definition.id, product.definition.storeSpecificId, product.definition.type.ToString(), product.definition.enabled ? "enabled" : "disabled"));
    149. #else
    150.                 ZLog.Write(string.Format("id: {0}\nstore-specific id: {1}\ntype: {2}\n", product.definition.id,
    151.                     product.definition.storeSpecificId, product.definition.type.ToString()));
    152. #endif
    153.         }
    154.     }
    155.  
    156.     /// <summary>
    157.     /// Restore purchased items after app is restarted
    158.     /// </summary>
    159.     private void InitPurchase()
    160.     {
    161.         bool verifiedProduct = false;
    162.         string str = "";
    163.         CrossPlatformValidator validator = null;
    164.  
    165.         foreach (Product product in m_StoreController.products.all)
    166.             if (product.hasReceipt && !string.IsNullOrEmpty(product.receipt))
    167.             {
    168.                 if (validator == null)
    169.                     validator = new CrossPlatformValidator(GooglePlayTangle.Data(),
    170.                                     AppleTangle.Data(), Application.identifier);
    171.  
    172.                 verifiedProduct = false;
    173.  
    174.                 try
    175.                 {
    176.                     var result = validator.Validate(product.receipt);
    177.  
    178.                     foreach (IPurchaseReceipt productReceipt in result)
    179.                     {
    180.                         //check that parsed receipt has our product id
    181.                         if (productReceipt.productID.Equals(product.definition.id))
    182.                             verifiedProduct = true;
    183.                         else
    184.                             str = productReceipt.productID;
    185.                     }
    186.  
    187.                 }
    188.                 catch (IAPSecurityException)
    189.                 {
    190.                     ZLog.Write("InitPurchase: " + product.definition.id + " Invalid receipt, not unlocking content. Fake receipt?");
    191.                     ErrorWindow.PopupError("InitPurchase: \n" + product.definition.id + "\n" +
    192.                         "Invalid receipt, not unlocking content. Fake receipt?");
    193.                     continue;
    194.                 }
    195.  
    196.                 if (verifiedProduct)
    197.                 {
    198.                     PurchaseSuccess(product.definition.id);
    199.                 }
    200.                 else
    201.                 {
    202.                     ZLog.Write("InitPurchase: " + product.definition.id + " Invalid receipt, not unlocking content. Fake receipt?");
    203.                     ErrorWindow.PopupError("InitPurchase: \n" + product.definition.id + "\n" +
    204.                         "Invalid receipt, not unlocking content. Fake receipt?");
    205.                 }
    206.  
    207.             }
    208.  
    209.         if (validator == null)
    210.             ZLog.Write("InitPurchase: no receipts");
    211.     }
    212.  
    213.     public void OnInitializeFailed(InitializationFailureReason error)
    214.     {
    215.         ZLog.Write("OnInitializeFailed: " + error);
    216.         ErrorWindow.PopupError("OnInitializeFailed: " + error);
    217.     }
    218.  
    219.     /// <summary>
    220.     /// Player press Buy button
    221.     /// </summary>
    222.     public void ButtonBuy(string productId)
    223.     {
    224.         BuyProductID(productId);
    225.     }
    226.  
    227.     private void BuyProductID(string productId)
    228.     {
    229.         try
    230.         {
    231.             if (IsInitialized())
    232.             {
    233.                 Product product = m_StoreController.products.WithID(productId);
    234.  
    235.                 if (product != null && product.availableToPurchase)
    236.                 {
    237.                     m_StoreController.InitiatePurchase(product);
    238.                 }
    239.                 else
    240.                 {
    241.                     ZLog.Write("BuyProductID: " + productId + " FAIL. " +
    242.                         "Not purchasing product, either is not found or is not available " +
    243.                         "for purchase");
    244.                     ErrorWindow.PopupError("BuyProductID: " + productId + " FAIL. \n" +
    245.                         "Not purchasing product, either is not found or is not available " +
    246.                         "for purchase");
    247.                 }
    248.             }
    249.             else
    250.             {
    251.                 ZLog.Write("BuyProductID: FAIL. " +
    252.                     "IAP manager is not initialized.");
    253.                 ErrorWindow.PopupError("BuyProductID: FAIL. \n" +
    254.                     "IAP manager is not initialized.");
    255.             }
    256.         }
    257.         catch (Exception e)
    258.         {
    259.             ErrorWindow.PopupError("BuyProductID: FAIL. Exception during purchase. " + e);
    260.         }
    261.     }
    262.  
    263.     /// <summary>
    264.     /// Action after successfull purchase, we add points
    265.     /// </summary>
    266.     private void PurchaseSuccess(string id)
    267.     {
    268.         PointsSystem.PurchasedPoints(id);
    269.     }
    270.  
    271.     public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args)
    272.     {
    273.         bool validPurchase = true; // Presume valid for platforms with no R.V.
    274.  
    275.         // Unity IAP's validation logic is only included on Android & iOS platforms.
    276.         // Prepare the validator with the secrets we prepared in the Editor
    277.         // obfuscation window.
    278.  
    279.         //TEMP
    280.         ZLog.Write("Purchase OK: " + args.purchasedProduct.definition.id);
    281.         ZLog.Write("Receipt: " + args.purchasedProduct.receipt);
    282.  
    283.         string str = "";
    284.         var validator = new CrossPlatformValidator(GooglePlayTangle.Data(),
    285.             AppleTangle.Data(), Application.identifier);
    286.  
    287.         try
    288.         {
    289.             validPurchase = false;
    290.             // On Google Play, result will have a single product Id.
    291.             var result = validator.Validate(args.purchasedProduct.receipt);
    292.             // For informational purposes, we list the receipt(s)
    293.             //Debug.Log("Receipt is valid. Contents:");
    294.  
    295.             foreach (IPurchaseReceipt productReceipt in result)
    296.             {
    297.                 //check that parsed receipt has our product id
    298.                 if (productReceipt.productID.Equals(args.purchasedProduct.definition.id))
    299.                     validPurchase = true;
    300.                 else
    301.                     str = productReceipt.productID;
    302.             }
    303.  
    304.             if (!validPurchase)
    305.             {
    306.                 ZLog.Write("ProcessPurchase: " + args.purchasedProduct.definition.id + "-" + str + " Product ID Receipt verification failed. Fake receipt?");
    307.                 ErrorWindow.PopupError("ProcessPurchase: \n" + args.purchasedProduct.definition.id + "\n" +
    308.                     "Product ID Receipt verification failed. Fake receipt?");
    309.                 return PurchaseProcessingResult.Complete;
    310.             }
    311.         }
    312.         catch (IAPSecurityException)
    313.         {
    314.             ZLog.Write("ProcessPurchase: " + args.purchasedProduct.definition.id + " Invalid receipt, not unlocking content. Fake receipt?");
    315.             ErrorWindow.PopupError("ProcessPurchase: \n" + args.purchasedProduct.definition.id + "\n" +
    316.                 "Invalid receipt, not unlocking content. Fake receipt?");
    317.  
    318.             return PurchaseProcessingResult.Complete;
    319.         }
    320.  
    321.         if (validPurchase)
    322.         {
    323.             PurchaseSuccess(args.purchasedProduct.definition.id);
    324.             ZLog.Write("ProcessPurchase: OK " + args.purchasedProduct.definition.id);
    325.         }
    326.  
    327.         return PurchaseProcessingResult.Complete;
    328.     }
    329.  
    330.     public void OnPurchaseFailed(Product product, PurchaseFailureReason failureReason)
    331.     {
    332.         if (failureReason == PurchaseFailureReason.UserCancelled ||
    333.            failureReason == PurchaseFailureReason.PaymentDeclined)
    334.             return;
    335.  
    336.         string str1, str2;
    337.  
    338.         str1 = "Purchase failed: " +
    339.             "ProductID: " + product.definition.id +
    340.             "Reason: " + failureReason;
    341.  
    342.         str2 = "Store specific error code: " + m_TransactionHistoryExtensions.GetLastStoreSpecificPurchaseErrorCode();
    343.         if (m_TransactionHistoryExtensions.GetLastPurchaseFailureDescription() != null)
    344.         {
    345.             str2 += "\nPurchase failure description message: " +
    346.                       m_TransactionHistoryExtensions.GetLastPurchaseFailureDescription().message;
    347.         }
    348.  
    349.         ZLog.Write(str1);
    350.         ZLog.Write(str2);
    351.  
    352.         // Detailed debugging information
    353.  
    354.         ErrorWindow.PopupError(str1 + "\n" + str2);
    355.     }
    356. }
     
  2. Kennai

    Kennai

    Joined:
    Nov 1, 2018
    Posts:
    27
    What I tried to buy:
    1. Tried to buy 1000 points - google says - payment is OK but app showed error
    1. Tried to buy 2000 points - google says - payment is OK but app showed error

    Custom logs:

     
  3. Kennai

    Kennai

    Joined:
    Nov 1, 2018
    Posts:
    27
    Before I made this tests, I cleared game cache and Google Play cache
     
  4. Kennai

    Kennai

    Joined:
    Nov 1, 2018
    Posts:
    27
    I will try to update Unity to latest 2019 version, it seems there is available In App Purchasing v 3.0.1, maybe it will help me.
     
  5. Kennai

    Kennai

    Joined:
    Nov 1, 2018
    Posts:
    27
    Updated Unity to 2019.4.26f1 and installed In App Purchasing package v3.1.0.
    It solved my problem, no bugs anymore. Purchases are acknoledged, woohoo :)