Jennal
Jennal

Reputation: 87

How to get Unity IAP work with Google Play IAB?

I have complete all steps from Unity IAP Tutorial.

Unity IAP Setup

But my game is still being review in Google Play Console. I know I don't need to wait for review under internal testing. But all informations I can get from internet tell me that, at least, I need to put my app in Alpha state. If I am wrong, please let me know.

Alpha Test

And my code is like this:

using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Purchasing;
using Veewo.Systems.Base;

// Placing the Purchaser class in the CompleteProject namespace allows it to interact with ScoreManager, 
// one of the existing Survival Shooter scripts.
namespace Veewo.Systems
{
    // Deriving the Purchaser class from IStoreListener enables it to receive messages from Unity Purchasing.
    public class IAPSystem : SystemBase, IStoreListener
    {
        private static IStoreController m_StoreController;          // The Unity Purchasing system.
        private static IExtensionProvider m_StoreExtensionProvider; // The store-specific Purchasing subsystems.

        // Product identifiers for all products capable of being purchased: 
        // "convenience" general identifiers for use with Purchasing, and their store-specific identifier 
        // counterparts for use with and outside of Unity Purchasing. Define store-specific identifiers 
        // also on each platform's publisher dashboard (iTunes Connect, Google Play Developer Console, etc.)

        // General product identifiers for the consumable, non-consumable, and subscription products.
        // Use these handles in the code to reference which product to purchase. Also use these values 
        // when defining the Product Identifiers on the store. Except, for illustration purposes, the 
        // kProductIDSubscription - it has custom Apple and Google identifiers. We declare their store-
        // specific mapping to Unity Purchasing's AddProduct, below.
        public static string kProductIDConsumable =    "veewo.pi.diamond600";   
        public static string kProductIDNonConsumable = "veewo.pi.limited.goldheropiece";
        public static string kProductIDSubscription =  "subscription"; 

        // Apple App Store-specific product identifier for the subscription product.
        private static string kProductNameAppleSubscription =  "com.unity3d.subscription.new";

        // Google Play Store-specific product identifier subscription product.
        private static string kProductNameGooglePlaySubscription =  "com.unity3d.subscription.original";

        protected override void OnInitialize()
        {
            base.OnInitialize();
            // If we haven't set up the Unity Purchasing reference
            if (m_StoreController == null)
            {
                // Begin to configure our connection to Purchasing
                InitializePurchasing();
            }
        }

        public void InitializePurchasing() 
        {
            Debug.Log("InitializePurchasing");
            // If we have already connected to Purchasing ...
            if (IsInitialized())
            {
                // ... we are done here.
                return;
            }

            // Create a builder, first passing in a suite of Unity provided stores.
            ConfigurationBuilder builder;
//            if (Application.platform == RuntimePlatform.Android
//                && StandardPurchasingModule.Instance().appStore == AppStore.GooglePlay)
//            {
//                builder = ConfigurationBuilder.Instance(
//                    Google.Play.Billing.GooglePlayStoreModule.Instance());
//            }
//            else
//            {
                builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());
//            }

            // Add a product to sell / restore by way of its identifier, associating the general identifier
            // with its store-specific identifiers.
            builder.AddProduct(kProductIDConsumable, ProductType.Consumable);
            // Continue adding the non-consumable product.
            builder.AddProduct(kProductIDNonConsumable, ProductType.NonConsumable);
            // And finish adding the subscription product. Notice this uses store-specific IDs, illustrating
            // if the Product ID was configured differently between Apple and Google stores. Also note that
            // one uses the general kProductIDSubscription handle inside the game - the store-specific IDs 
            // must only be referenced here. 
//            builder.AddProduct(kProductIDSubscription, ProductType.Subscription, new IDs(){
//                { kProductNameAppleSubscription, AppleAppStore.Name },
//                { kProductNameGooglePlaySubscription, GooglePlay.Name },
//            });

            // Kick off the remainder of the set-up with an asynchrounous call, passing the configuration 
            // and this class' instance. Expect a response either in OnInitialized or OnInitializeFailed.
            UnityPurchasing.Initialize(this, builder);
            Debug.Log("UnityPurchasing.Initialize");
        }


        private bool IsInitialized()
        {
            // Only say we are initialized if both the Purchasing references are set.
            return m_StoreController != null && m_StoreExtensionProvider != null;
        }


        public void BuyConsumable()
        {
            // Buy the consumable product using its general identifier. Expect a response either 
            // through ProcessPurchase or OnPurchaseFailed asynchronously.
            BuyProductID(kProductIDConsumable);
        }


        public void BuyNonConsumable()
        {
            // Buy the non-consumable product using its general identifier. Expect a response either 
            // through ProcessPurchase or OnPurchaseFailed asynchronously.
            BuyProductID(kProductIDNonConsumable);
        }


        public void BuySubscription()
        {
            // Buy the subscription product using its the general identifier. Expect a response either 
            // through ProcessPurchase or OnPurchaseFailed asynchronously.
            // Notice how we use the general product identifier in spite of this ID being mapped to
            // custom store-specific identifiers above.
            BuyProductID(kProductIDSubscription);
        }


        void BuyProductID(string productId)
        {
            // If Purchasing has been initialized ...
            if (IsInitialized())
            {
                // ... look up the Product reference with the general product identifier and the Purchasing 
                // system's products collection.
                Product product = m_StoreController.products.WithID(productId);

                // If the look up found a product for this device's store and that product is ready to be sold ... 
                if (product != null && product.availableToPurchase)
                {
                    Debug.Log(string.Format("Purchasing product asychronously: '{0}'", product.definition.id));
                    // ... buy the product. Expect a response either through ProcessPurchase or OnPurchaseFailed 
                    // asynchronously.
                    m_StoreController.InitiatePurchase(product);
                }
                // Otherwise ...
                else
                {
                    // ... report the product look-up failure situation  
                    Debug.Log("BuyProductID: FAIL. Not purchasing product, either is not found or is not available for purchase");
                }
            }
            // Otherwise ...
            else
            {
                // ... report the fact Purchasing has not succeeded initializing yet. Consider waiting longer or 
                // retrying initiailization.
                Debug.Log("BuyProductID FAIL. Not initialized.");
            }
        }


        // 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()
        {
            // If Purchasing has not yet been set up ...
            if (!IsInitialized())
            {
                // ... report the situation and stop restoring. Consider either waiting longer, or retrying initialization.
                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
                Debug.Log("RestorePurchases started ...");

                // 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.");
                });
            }
            // Otherwise ...
            else
            {
                // We are not running on an Apple device. No work is necessary to restore purchases.
                Debug.Log("RestorePurchases FAIL. Not supported on this platform. Current = " + Application.platform);
            }
        }


        //  
        // --- IStoreListener
        //

        public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
        {
            // Purchasing has succeeded initializing. Collect our Purchasing references.
            Debug.Log("OnInitialized: PASS");

            // Overall Purchasing system, configured with products for this application.
            m_StoreController = controller;
            // Store specific subsystem, for accessing device-specific store features.
            m_StoreExtensionProvider = extensions;
        }


        public void OnInitializeFailed(InitializationFailureReason error)
        {
            // Purchasing set-up has not succeeded. Check error for reason. Consider sharing this reason with the user.
            Debug.Log("OnInitializeFailed InitializationFailureReason:" + error);
        }


        public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args) 
        {
            // A consumable product has been purchased by this user.
            if (String.Equals(args.purchasedProduct.definition.id, kProductIDConsumable, StringComparison.Ordinal))
            {
                Debug.Log(string.Format("ProcessPurchase: PASS. Product: '{0}'", args.purchasedProduct.definition.id));
                // The consumable item has been successfully purchased, add 100 coins to the player's in-game score.
                //TODO:
            }
            // Or ... a non-consumable product has been purchased by this user.
            else if (String.Equals(args.purchasedProduct.definition.id, kProductIDNonConsumable, StringComparison.Ordinal))
            {
                Debug.Log(string.Format("ProcessPurchase: PASS. Product: '{0}'", args.purchasedProduct.definition.id));
                // TODO: The non-consumable item has been successfully purchased, grant this item to the player.
            }
            // Or ... a subscription product has been purchased by this user.
            else if (String.Equals(args.purchasedProduct.definition.id, kProductIDSubscription, StringComparison.Ordinal))
            {
                Debug.Log(string.Format("ProcessPurchase: PASS. Product: '{0}'", args.purchasedProduct.definition.id));
                // TODO: The subscription item has been successfully purchased, grant this to the player.
            }
            // Or ... an unknown product has been purchased by this user. Fill in additional products here....
            else 
            {
                Debug.Log(string.Format("ProcessPurchase: FAIL. Unrecognized product: '{0}'", args.purchasedProduct.definition.id));
            }

            // Return a flag indicating whether this product has completely been received, or if the application needs 
            // to be reminded of this purchase at next app launch. Use PurchaseProcessingResult.Pending when still 
            // saving purchased products to the cloud, and when that save is delayed. 
            return PurchaseProcessingResult.Complete;
        }


        public void OnPurchaseFailed(Product product, PurchaseFailureReason failureReason)
        {
            // A product purchase attempt did not succeed. Check failureReason for more detail. Consider sharing 
            // this reason with the user to guide their troubleshooting actions.
            Debug.Log(string.Format("OnPurchaseFailed: FAIL. Product: '{0}', PurchaseFailureReason: {1}", product.definition.storeSpecificId, failureReason));
        }

        public IEnumerable<Product> GetAllProducts()
        {
            if (!IsInitialized()) yield break;

            foreach (var product in m_StoreController.products.set)
            {
                yield return product;
            }
        }
    }
}

I am quite sure that product key is correct.

Product keys

But I still can't get this work. I tested in BlueStack4 and Android9 mobile, all failed. Can't get UnityPurchasing.Initialize done with OnInitialized called.

Logs in bluestack are:

10-29 09:51:17.193  5183  5203 I Unity   : UnityIAP Version: 2.1.1
10-29 09:51:17.219  5183  5203 I UnityIAP: Starting in-app billing setup.
10-29 09:51:17.221  5183  5183 I UnityIAP: Billing service connected.
10-29 09:51:17.222  5183  5203 I Unity   : UnityPurchasing.Initialize
10-29 09:51:17.224  5183  5238 I UnityIAP: Billing Service Manager invoking callback 1 of 1
10-29 09:51:17.224  5183  5238 I UnityIAP: Checking for in-app billing 3 support.
10-29 09:51:17.224  4671  4683 W Finsky  : [291] iec.a(16): com.veewo.alienland: No account found.
10-29 09:51:17.226  5183  5238 D UnityIAP: onIabSetupFinished: 3
10-29 09:51:17.226  5183  5238 I UnityIAP: Failed to setup IAB. Notifying Unity...
10-29 09:51:17.276  5183  5203 I Unity   : OnInitializeFailed InitializationFailureReason:PurchasingUnavailable
10-29 09:51:17.276  5183  5203 I Unity   : UnityEngine.Logger:Log(LogType, Object)
10-29 09:51:17.276  5183  5203 I Unity   : Veewo.Systems.IAPSystem:OnInitializeFailed(InitializationFailureReason)
10-29 09:51:17.276  5183  5203 I Unity   : UnityEngine.Purchasing.Extension.UnityUtil:Update()
10-29 09:51:17.276  5183  5203 I Unity   :
10-29 09:51:17.276  5183  5203 I Unity   : (Filename: ./Runtime/Export/Debug.bindings.h Line: 45)
10-29 09:51:17.276  5183  5203 I Unity   :
10-29 09:51:30.322  1962  3360 I NetworkScheduler.Stats: Task com.google.android.gms/com.google.android.gms.auth.account.be.legacy.AuthCronService started execution. cause:4 exec_start_elapsed_seconds: 1056 [CONTEXT service_id=218 ]
10-29 09:51:32.652  4671  4707 E Finsky  : [314] log.b(1): Error while refreshing device settings: network time: 32342, HTTP status code: na, exception com.android.volley.TimeoutError. Retrying.
10-29 09:51:32.652  4671  4707 E Finsky  : network time: 32342, HTTP status code: na, exception com.android.volley.TimeoutError
10-29 09:51:32.652  4671  4707 E Finsky  :      at vqf.hq(PG)
10-29 09:51:32.652  4671  4707 E Finsky  :      at czn.r(PG:2)
10-29 09:51:32.652  4671  4707 E Finsky  :      at exo.r(PG:3)
10-29 09:51:32.652  4671  4707 E Finsky  :      at czg.run(PG:3)
10-29 09:51:32.652  4671  4707 E Finsky  :      at android.os.Handler.handleCallback(Handler.java:751)
10-29 09:51:32.652  4671  4707 E Finsky  :      at android.os.Handler.dispatchMessage(Handler.java:95)
10-29 09:51:32.652  4671  4707 E Finsky  :      at android.os.Looper.loop(Looper.java:154)
10-29 09:51:32.652  4671  4707 E Finsky  :      at android.app.ActivityThread.main(ActivityThread.java:6138)
10-29 09:51:32.652  4671  4707 E Finsky  :      at java.lang.reflect.Method.invoke(Native Method)
10-29 09:51:32.652  4671  4707 E Finsky  :      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:893)
10-29 09:51:32.652  4671  4707 E Finsky  :      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:783)
10-29 09:51:32.653  4671  4704 E Finsky  : [311] lon.b(-1): Error getting device settings.
10-29 09:51:32.653  4671  4704 E Finsky  : network time: 32342, HTTP status code: na, exception com.android.volley.TimeoutError
10-29 09:51:32.653  4671  4704 E Finsky  :      at vqf.hq(PG)
10-29 09:51:32.653  4671  4704 E Finsky  :      at czn.r(PG:2)
10-29 09:51:32.653  4671  4704 E Finsky  :      at exo.r(PG:3)
10-29 09:51:32.653  4671  4704 E Finsky  :      at czg.run(PG:3)
10-29 09:51:32.653  4671  4704 E Finsky  :      at android.os.Handler.handleCallback(Handler.java:751)
10-29 09:51:32.653  4671  4704 E Finsky  :      at android.os.Handler.dispatchMessage(Handler.java:95)
10-29 09:51:32.653  4671  4704 E Finsky  :      at android.os.Looper.loop(Looper.java:154)
10-29 09:51:32.653  4671  4704 E Finsky  :      at android.app.ActivityThread.main(ActivityThread.java:6138)
10-29 09:51:32.653  4671  4704 E Finsky  :      at java.lang.reflect.Method.invoke(Native Method)
10-29 09:51:32.653  4671  4704 E Finsky  :      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:893)
10-29 09:51:32.653  4671  4704 E Finsky  :      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:783)
10-29 09:51:32.653  4671  4707 I Finsky  : [314] abij.n(4): SCH: jobFinished: 31-305419896. TimeElapsed: 32344ms.
10-29 09:51:32.653  4671  4671 I Finsky  : [1] abgy.c(7): SCH: Job 31-305419896 finished. Rescheduling.
10-29 09:51:32.653  4671  4671 I Finsky  : [1] abgx.handleMessage(60): SCH: RunningQueue size: 0, PendingQueue size: 0
10-29 09:51:32.654  4671  4671 I Finsky  : [1] abgx.handleMessage(26): SCH: Executor finished
10-29 09:51:32.658  4671  4671 I Finsky  : [1] abjk.b(5): SCH: Jobs in database: 1-1337 24-77777777 26-1414141414 31-305419896
10-29 09:51:32.659  4671  4671 I Finsky  : [1] abhw.d(37): SCH: ConstraintMapping: 24-77777777,  -> L: 0ms, D: 24834708ms, C: false, I: true, N: 0
10-29 09:51:32.660  4671  4671 I Finsky  : [1] abhw.d(37): SCH: ConstraintMapping: 1-1337, 31-305419896,  -> L: 127994ms, D: 32437706ms, C: false, I: false, N: 1
10-29 09:51:32.660  4671  4671 I Finsky  : [1] abhw.d(37): SCH: ConstraintMapping: 26-1414141414,  -> L: 29458222ms, D: 30358222ms, C: false, I: false, N: 0
10-29 09:51:32.662  4671  4671 I Finsky  : [1] abhw.f(7): SCH: Cancelling existing jobscheduler jobs: 9000 9005
10-29 09:51:32.662  4671  4671 I Finsky  : [1] abhw.e(23): SCH: Scheduling job Id: 9001, L: 0, D: 24834708, C: false, I: true, N: 0
10-29 09:51:32.663  4671  4671 I Finsky  : [1] abhw.e(23): SCH: Scheduling job Id: 9002, L: 127994, D: 32437706, C: false, I: false, N: 1
10-29 09:51:32.664  4671  4671 I Finsky  : [1] abhw.e(23): SCH: Scheduling job Id: 9003, L: 29458222, D: 30358222, C: false, I: false, N: 0

It is been 4 days that I can't get this work. All my guess are:

  1. Network problem, I am in China, and can't get connect to Google Service, although I use VPN, but it is not stable.
  2. I don't setup Unity IAP correct
  3. I need to wait for Google Play Console review finished, and wait for its state become published.
  4. I don't use correct Google Account login in my phone. I use the same account to google play account, and I already add it to test account list, should I use a new account?

I am so confused now, and don't know what to do next. Please help me.

Upvotes: 1

Views: 3450

Answers (1)

Jay Mencfel
Jay Mencfel

Reputation: 93

Seems like your code is correct. But your app needs to be released already. If it's in review it's not gonna work. Just wait fill it's approved.

Upvotes: 1

Related Questions