How can I acknowledge purchases received through the GooglePlay Billing Library with the IAP button

Hi, recently there was an IAP purchase made in my game for a non-consumable product which was to remove ads but google refunded the purchase back to the user 3 days later. When I reached out to google then it was like you must acknowledge the payments received through GooglePlay Billing Library.
Here is the reply:

[quote]*
Please kindly note that Google Play supports purchasing products from inside of your app (in-app) or outside of your app (out-of-app). In order for Google Play to ensure a consistent purchase experience regardless of where the user purchases your product, you must acknowledge all purchases received through the Google Play Billing Library as soon as possible after granting entitlement to the user. If you do not acknowledge a purchase within three days, the user automatically receives a refund, and Google Play revokes the purchase
[/quote]
.

On asking how do I do that the person concerned says he can’t help me further and drops a link, I simply get a link but cant figure out how I implement that in my In-App purchase button. The link is [here ](http:// Google Play 结算库版本说明  |  Google Play's billing system  |  Android Developers).

I saw a similar thread somewhere but there it was said that the version of IAP purchase button 2.2.7. But I was using 4.1… something and just updated it to the latest version of 4.2.1.

This is the simple code I have written in my game:

public void OnPurchaseComplete(Product product)
{
SFXManager.PlaySound(“button”);
if (product.definition.id==CancelAds)
{
Debug.Log(“Purchase successful”);
PlayerPrefs.SetInt(“Ads”, (int)AdsStatus.SuccessfullyPurchased);
NoAdsButton.SetActive(false);
SceneManager.LoadScene(“Menu”);

}
}

public void OnPurchaseFailed(Product product,PurchaseFailureReason purchaseFailureReason)
{
MessageFormatter(“Sorry Purchase Failed. Try again Later”);
Debug.Log(“Purchase failed”);
PlayerPrefs.SetInt(“Ads”, (int)AdsStatus.NotPurchased);
}

public void OnPurchaseComplete(Product product)
    {
        SFXManager.PlaySound("button");
        if (product.definition.id==CancelAds)
        {
            Debug.Log("Purchase successful");
            PlayerPrefs.SetInt("Ads", (int)AdsStatus.SuccessfullyPurchased);
            NoAdsButton.SetActive(false);
            SceneManager.LoadScene("Menu");
           
        }
    }

    public void OnPurchaseFailed(Product product,PurchaseFailureReason purchaseFailureReason)
    {
        MessageFormatter("Sorry Purchase Failed. Try again Later");   
        Debug.Log("Purchase failed");
        PlayerPrefs.SetInt("Ads", (int)AdsStatus.NotPurchased);
    }

@Chethan007 Do not use Codeless IAP buttons, they need updating. I would suggest you start with this sample IAP project here Sample IAP Project

Hi thanks for the help and sorry for such a late reply. I did try to download that IAP project but there was nothing in the method when the non-consumable button was clicked linked to.

You can see the screenshot here.

I tried to look into the next part of the tutorial of the OP where your answer points to in that channel but couldn’t find anywhere. Could you please give me some other link as to where I can get my answer.

Also why can’t codeless IAP buttons do the acknowledgment function stuff?

Please use the Sample IAP Project and obviously add the same code to purchase a non-consumable, compare to the line just above the one in your screenshot. Yes, don’t use Codeless, it hasn’t been updated in some time.

You mean to say the code
BuyProductID(GOLD_50);
or the code which I gave in my OP.

Also how do you acknowledge the payments received through GooglePlay Billing Library?

Should I use the normal button or the IAP button as of now?

Test with the Sample Project with as few changes as possible. GOLD_50 is a consumable, you’ll want to test with a non-consumable. Do not use Codeless IAP Buttons. Unity IAP (and the Sample Project) already acknowledges payments, once you get it working.

Sorry If I am irritating you , but should I use the normal button or the IAP button? I really dont get it when you say test with sample project because I can only really test it when I put it on the Play Store? Also you have that Google Play Csv file which you have to upload it in the play console. How do I do that also?

As I mentioned, do not use a Codeless IAP Button. You don’t use a csv file to upload your app to Google Play, and you don’t need to do so for your products either. And yes, you must test IAP from the Play Store. So create a brand new application with the Sample Project to test with and publish it on Google Play once it is compiling and running in the Editor. Then use lessons learned from this test project in your core game. Having two projects like this on Google Play avoids having your core project “broken” while you learn and test new technologies like IAP.

Hi , I tried modifiying the existing project and do. But there is some problem now. When you click on the cancel ads button in my game , the menu to purchase though it appears which asks for the various payments, even if you dont pay anything and click the the back button while purchasing the no ads payment is successful. Here is the code which I have written. Would you mind saying what wrong am I doing here?

 using System;
using System.Collections.Generic;
using System.Collections;
using UnityEngine;
using UnityEngine.Purchasing;
using UnityEngine.UI;
using UnityEngine.Analytics;
using UnityEngine.Purchasing.Security;
using UnityEngine.SceneManagement;

public class IAPManager_New : MonoBehaviour, IStoreListener
{
    private static IStoreController m_StoreController;          // The Unity Purchasing system.
    private static IExtensionProvider m_StoreExtensionProvider; // The store-specific Purchasing subsystems.
    private static UnityEngine.Purchasing.Product test_product = null;
    private IAppleExtensions m_AppleExtensions;
    private IGooglePlayStoreExtensions m_GoogleExtensions;
    [SerializeField] private Text outputMessage;

    public static string CancelAds = "com.chethan.flappybirdsuniverse.cancelads";
    private ConfigurationBuilder builder;
    [SerializeField] GameObject NoAdsButton;
    [SerializeField] GameObject PaidButton;
    [SerializeField] private GameObject MessageWindow;
    [SerializeField] private float faderTimeforWindow = 0.3f;//see inspector for final values
    private Boolean return_complete = true;

    // Start is called before the first frame update
    void Start()
    {
       // If we haven't set up the Unity Purchasing reference
        if (m_StoreController == null)
        {
            // Begin to configure our connection to Purchasing
            InitializePurchasing();
        }
        Debug.Log("In Start of IAP Manager new..."); 
        Debug.Log("PlayerPrefs.GetInt(\"Ads\")..."+PlayerPrefs.GetInt("Ads")); 
        //PlayerPrefs.SetInt("Ads",(int)AdsStatus.NotPurchased);
        if (PlayerPrefs.GetInt("Ads")==(int)AdsStatus.SuccessfullyPurchased)
        {
            Debug.Log("SuccessfullyPurchased executed...................");
            NoAdsButton.SetActive(false);
            PaidButton.SetActive(true);
        }
         if (PlayerPrefs.GetInt("Ads")==(int)AdsStatus.NotPurchased)
        {
            Debug.Log("Not purchased executed...................");
            NoAdsButton.SetActive(true);
            PaidButton.SetActive(false);
        }
    }

    public void OnClickCancelAdsButton()
    {
      BuyProductID(CancelAds);
    }

     void BuyProductID(string productId)
    {
        if (IsInitialized())
        {
            UnityEngine.Purchasing.Product product = m_StoreController.products.WithID(productId);

            if (product != null && product.availableToPurchase)
            {
                Debug.Log(string.Format("Purchasing product:" + product.definition.id.ToString()));
                m_StoreController.InitiatePurchase(product);
                PlayerPrefs.SetInt("Ads", (int)AdsStatus.SuccessfullyPurchased);
                NoAdsButton.SetActive(false);
                PaidButton.SetActive(true);
                SceneManager.LoadScene("Menu");
            }
            else
            {
                 MessageFormatter("Sorry Purchase Failed. Try again Later");  
                 Debug.Log("BuyProductID: FAIL. Not purchasing product, either is not found or is not available for purchase");
                PlayerPrefs.SetInt("Ads", (int)AdsStatus.NotPurchased);
                 NoAdsButton.SetActive(true);
                PaidButton.SetActive(false);
            }
        }
        else
        {
            MessageFormatter("Sorry Purchase Failed. Try again Later...."); 
            Debug.Log("BuyProductID FAIL. Not initialized.");
             PlayerPrefs.SetInt("Ads", (int)AdsStatus.NotPurchased);
            NoAdsButton.SetActive(true);
            PaidButton.SetActive(false);
        }
    }
    private bool IsInitialized()
    {
        return m_StoreController != null && m_StoreExtensionProvider != null;
    }

    public void MyInitialize()
    {
        InitializePurchasing();
    }

    public void InitializePurchasing()
    {
        if (IsInitialized())
        {
            return;
        }
       
        builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());

        builder.AddProduct(CancelAds, ProductType.NonConsumable);
        // builder.AddProduct(MONTHLY, ProductType.Subscription);
        // builder.AddProduct(YEARLY, ProductType.Subscription);

        builder.Configure<IGooglePlayConfiguration>().SetObfuscatedAccountId("test1");

        builder.Configure<IGooglePlayConfiguration>().SetDeferredPurchaseListener(OnDeferredPurchase);

        Debug.Log("Starting Initialized...");
        UnityPurchasing.Initialize(this, builder);

        //ProductCatalog catalog = ProductCatalog.LoadDefaultCatalog();

        //foreach (ProductCatalogItem product in catalog.allProducts)
        // {
        //MyDebug("Product = " + product.id);
        //}
    }
     void OnDeferredPurchase(Product product)
    {
        Debug.Log($"Purchase of {product.definition.id} is deferred");
    }

     private void MessageFormatter(string Message)
    {
        outputMessage.text = "";//each time u call this function make sure the output is fresh to display
        FadeStarterForMessageModal(MessageWindow);
        string[] words = Message.Split(' ');
        int count = 0;
        foreach (string word in words)
        {
            count = count + 1;
            if (count % 3 == 0)
                outputMessage.text = outputMessage.text + " " + word + "\n";
            else if (count > 15)//not more than 5 line for that message
                break;
            else
                outputMessage.text = outputMessage.text + " " + word;
        }
    }
    private void FadeStarterForMessageModal(GameObject Window)
    {
        SFXManager.PlaySound("button");
        Window.SetActive(true);
        LeanTween.scale(Window, Vector3.zero, 0f);//
        LeanTween.scale(Window, new Vector3(1, 1, 1), faderTimeforWindow).
            setOnComplete(() => Window.SetActive(true)).setIgnoreTimeScale(true);
    }

    // Update is called once per frame
    void Update()
    {
       
    }
    public void CompletePurchase()
    {
        if (test_product == null)
            Debug.Log("Cannot complete purchase, product not initialized.");
        else
        {
            m_StoreController.ConfirmPendingPurchase(test_product);
            //FetchProducts();
          
            Debug.Log("Completed purchase with " + test_product.definition.id.ToString());
        }
    }

    #region IStoreListenerCallBackFunctions

   
    public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
    {
        Debug.Log("OnInitialized: PASS");

        m_StoreController = controller;
        m_StoreExtensionProvider = extensions;
        m_AppleExtensions = extensions.GetExtension<IAppleExtensions>();
        m_GoogleExtensions = extensions.GetExtension<IGooglePlayStoreExtensions>();

        Dictionary<string, string> dict = m_AppleExtensions.GetIntroductoryPriceDictionary();

        foreach (UnityEngine.Purchasing.Product item in controller.products.all)
        {
            if (item.receipt != null)
            {
                string intro_json = (dict == null || !dict.ContainsKey(item.definition.storeSpecificId)) ? null : dict[item.definition.storeSpecificId];

                if (item.definition.type == ProductType.Subscription)
                {
                    SubscriptionManager p = new SubscriptionManager(item, null);
                    SubscriptionInfo info = p.getSubscriptionInfo();
                    Debug.Log("SubInfo: " + info.getProductId().ToString());
                    Debug.Log("getExpireDate: " + info.getExpireDate().ToString());
                    Debug.Log("isSubscribed: " + info.isSubscribed().ToString());
                }
            }
        }
    }

    public void OnInitializeFailed(InitializationFailureReason error)
    {
        Debug.Log("OnInitializeFailed InitializationFailureReason:" + error);
    }

    public void OnPurchaseFailed(Product product, PurchaseFailureReason failureReason)
    {
         Debug.Log(string.Format("OnPurchaseFailed: FAIL. Product: '{0}', PurchaseFailureReason: {1}", product.definition.storeSpecificId, failureReason));
    }

    public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs purchaseEvent)
    {
         test_product = purchaseEvent.purchasedProduct;

        //var validator = new CrossPlatformValidator(GooglePlayTangle.Data(), AppleTangle.Data(), Application.identifier);

        // var result = validator.Validate(args.purchasedProduct.receipt);

        // foreach (IPurchaseReceipt productReceipt in result)
        // {
        //     MyDebug("Valid receipt for " + productReceipt.productID.ToString());
        // }

        //MyDebug("Validate = " + result.ToString());

        Debug.Log("Receipt:" + test_product.receipt.ToString());

        if (return_complete)
        {
            Debug.Log(string.Format("ProcessPurchase: Complete. Product:" + purchaseEvent.purchasedProduct.definition.id + " - " + test_product.transactionID.ToString()));
            return PurchaseProcessingResult.Complete;
        }
        else
        {
            Debug.Log(string.Format("ProcessPurchase: Pending. Product:" + purchaseEvent.purchasedProduct.definition.id + " - " + test_product.transactionID.ToString()));
            return PurchaseProcessingResult.Pending;
        }
    }

    #endregion
}

I honestly dont understand the code , but I have done my best as to whatever I can do. under the line of the code

if (product != null && product.availableToPurchase)

whatever is under the if codition that part is getting executed.

Would you mind pointing me to some tutorial where without the codeless IAP button the In-App purchase is looked into. Majority of the tutorials use the codeless button, but I have used the normal button as you said not to use the codeless button.

@Chethan007 You have modified the Sample project and now it doesn’t work. Please get the Sample working with no changes at all. Get the basics working first. Create a new app on your Google developer dashboard with the Sample project. Ensure to follow the directions here How to Set Up | In App Purchasing | 4.4.1

@JeffDUnity3D Ok , even if I submit the Sample, I have to get do few small changes because under the line of code

 public void BuyNonConsumable()
    {
      
    }

there isn’t anything.So I have just written the following code

public static string NO_ADS="cancel_ads";
  public void BuyNonConsumable()
    {
        BuyProductID(NO_ADS);
    }

But I just don’t where is the code I have to write if a successful purchase has to be made. I really dont understand the code and all I am asking you is give the code which is working.

Why can’t the Codeless IAP buttons,be updated as such

I tried to submit the sample project to the play store, but I dont think that it is even a game for that matter. Should I really make a sample game from scratch and work all the way up with the privacy policy and all that stuff?

Just publish the Sample to Closed testing without any changes except for the one you posted, that is ok. I have published it myself, it doesn’t need to be an actual game. You award the product in ProcessPurchase. This code is all working.

So here is the thing. After 2 days my Sample game was approved from google.

When I clicked on the initialize function, it said initialize fail. Because I didnt add the following code in the InitializePurchasing function earlier
builder.AddProduct(NO_ADS, ProductType.NonConsumable);
So I added the above line,along with the following in FetchProducts function:

 ProductDefinition item2 = new ProductDefinition(NO_ADS, ProductType.NonConsumable);
myHashSet.Add(item2);

Later after my game got just approved now, I did an inapp purchase and the purchase was successful. You can see the screenshots here and here

Is this much fine, or should I do more, because if you see game screen screenshot, it says acknowledged as false(correct me if I am wrong as next to acknowledged in that huge string it says false, may be it is not meant for acknowledged also). Will this code cause the same issue as before wherein which if the user makes a purchase a refund will be issued?

Yes, all good! You are properly returning Complete from ProcessPurchase