Unity内IAP支付二次验证/服务器验证————最白话,手把手教你做系列。

之前的一篇写了Unity支付的IAP支付接入。
后来就出现了一些问题,数据统计的时候出现大量购买订单。但是实际上账户的钱却没有增加。@¥……&¥……*@¥&@初步判定可能存在部分用户通过其他渠道刷单的现象,然后才有以下操作,验证用户的订单是否的真实有效。
原理很简单,就是用户购买的时候我们把用户的购买信息发送给Goole/APPLE做个验证。然后通过返回的信息判断是否是有效订单,最后在游戏内执行相应逻辑。
这里我们是通过服务器验证,也就是需要
1,我们把信息发到我们的指定服务器(不是直接发送给Goole/APPLE,网上写的这种方法更安全)。
2,指定服务器端进行验证逻辑并给出返回值。(这点可以交给服务端的小伙伴做,服务器什么的我也不太懂)。
3,我们把返回值做解析然后关联客户端逻辑。
理论上1,3是要在Unity内进行的。

下面是代码部分,需要的可以对照一下IAP支付的代码看一下[链接地址][http://blog.csdn.net/qq_39860954/article/details/78880767]

using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Purchasing;

// Placing the Purchaser class in the CompleteProject namespace allows it to interact with ScoreManager, 
// one of the existing Survival Shooter scripts.
// Deriving the Purchaser class from IStoreListener enables it to receive messages from Unity Purchasing.
//脚本需在调用购买方法之前初始化
    public class Purchaser : MonoBehaviour, IStoreListener

    {  //定义商品
        private const string product_1 = "商品ID1";
        private const string product_2 = "商品ID2";
        private const string product_3 = "商品ID3";

        private static IStoreController m_StoreController;          // The Unity Purchasing system.
        private static IExtensionProvider m_StoreExtensionProvider; // The store-specific Purchasing subsystems.

        public static string kProductIDConsumable = "consumable";
        public static string kProductIDNonConsumable = "nonconsumable";
        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";
        void Start()
        {
            // 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()
        {
            // 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.
            var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());

            //添加商品ID和类型 对应定义的商品ID
            builder.AddProduct(product_1, ProductType.Consumable, new IDs
            {
                {"商品ID1", GooglePlay.Name }
            });
            builder.AddProduct(product_2, ProductType.Consumable, new IDs
            {
                {"商品ID1", GooglePlay.Name }
            });
            builder.AddProduct(product_3, ProductType.Consumable, new IDs
            {
                {"商品ID1", 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);
        }
        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);
        }

    //购买商品调用的方法
        public 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)
        {
        //获取并解析你需要上传的数据。解析成string类型
        var wrapper = (Dictionary<string, object>) MiniJson.JsonDecode (args.purchasedProduct.receipt);
        if (null == wrapper) {
            return PurchaseProcessingResult.Complete;
        }

        // Corresponds to http://docs.unity3d.com/Manual/UnityIAPPurchaseReceipts.html
        var store = (string)wrapper ["Store"];
        //下面的payload 即为IOS的验证商品信息的数据。即我们需要上传的部分。
        var payload = (string)wrapper ["Payload"]; // For Apple this will be the base64 encoded ASN.1 receipt

#if UNITY_IPHONE
        StartCoroutine(PostRepict("http://www.xxxxxxxxxxxxxx/purchase/Verifytrade",args.purchasedProduct.definition.id,payload));

#endif


        //For GooglePlay payload contains more JSON
        if (Application.platform == RuntimePlatform.Android)
        {
            var gpDetails = (Dictionary<string, object>)MiniJson.JsonDecode(payload);
            var gpJson = (string)gpDetails["json"];
            var gpSig = (string)gpDetails["signature"];

    //Google验证商品信息的数据包含在gpJson里面还需要在服务端进行解析一下,对应的键是"purchaseToken"。
            StartCoroutine(PostRepict("http://www.xxxxxxxxxxxxx/purchase/Andverifytrade", args.purchasedProduct.definition.id, gpJson));
        }

            // A consumable product has been purchased by this user.

        // 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;
        }
    //Unity内传输信息到服务器的方法,www:服务器网址,payID:商品ID,验证商品的数据。 Google需要传商品ID(必要)IOS(不必要)。
        IEnumerator PostRepict(string www,string payID,string gpJson)

    {
        WWWForm form  = new WWWForm();  
        form.AddField("productId",payID);  
        form.AddField("purchaseToken",gpJson);  
        WWW getData = new WWW(www,form); 

        yield return getData;  

        if(getData.error!= null)  
        {  
            Debug.Log(getData.text);  
        }   
        //srcString即为服务器返回信息。
        string srcString = getData.text;  

        try {
        //解析服务器返回的数据,根据服务器端设定的逻辑进行判断,比如我这里是如果返回数据中"code"的值为200则为真实订单,所以在判定成功时继续客户端逻辑。
            JSONNode jsonNode = JSONNode.Parse(srcString);

            if(jsonNode["code"].AsInt==200)
            {
                //判定为真实订单,这里我把游戏内购买逻辑放在这个位置
                  if (String.Equals(payID., product_1, StringComparison.Ordinal))
            {
                        //商品1购买成功逻辑

            } else if (String.Equals(payID, product_2, StringComparison.Ordinal)) {

                        //商品2购买成功逻辑     
            }
            else if (String.Equals(payID, product_3, StringComparison.Ordinal))
            {
                        //商品3购买成功逻辑
            }


            }else
            {
             Debug.Log(string.Format("ProcessPurchase: FAIL. Unrecognized product: '{0}'", args.purchasedProduct.definition.id));
            //判定为假订单
            }

        } catch(System.Exception e) {
            Debug.LogError ("Server Error:" + e.Message);
            //其他网络问题

        }

    }

        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));
        }
    }

以上代码已验证可行,但仍有部分可优化的地方。
1,因为支付后的逻辑是用协程(StartCoroutine)做的,所以支付可能会出现支付成功但没有即时数据更新。
2,支付失败也没有另外做处理,比如显示支付失败的页面。
3,二次验证只是作为购买部分的一套处理逻辑,对于不通过虚假购买而是直接破解改动游戏数值的情况并没有什么卵用。

发布了27 篇原创文章 · 获赞 26 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qq_39860954/article/details/79315982