Unity內IAP支付二次驗證/伺服器驗證————最白話,手把手教你做系列。
阿新 • • 發佈:2019-01-22
之前的一篇寫了Unity支付的IAP支付接入。
後來就出現了一些問題,資料統計的時候出現大量購買訂單。但是實際上賬戶的錢卻沒有增加。@¥……&¥……*@¥&@初步判定可能存在部分使用者通過其他渠道刷單的現象,然後才有以下操作,驗證使用者的訂單是否的真實有效。
原理很簡單,就是使用者購買的時候我們把使用者的購買資訊傳送給Goole/APPLE做個驗證。然後通過返回的資訊判斷是否是有效訂單,最後在遊戲內執行相應邏輯。
這裡我們是通過伺服器驗證,也就是需要
1,我們把資訊發到我們的指定伺服器(不是直接傳送給Goole/APPLE,網上寫的這種方法更安全)。
2,指定伺服器端進行驗證邏輯並給出返回值。(這點可以交給服務端的小夥伴做,伺服器什麼的我也不太懂)。
3,我們把返回值做解析然後關聯客戶端邏輯。
理論上1,3是要在Unity內進行的。
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,二次驗證只是作為購買部分的一套處理邏輯,對於不通過虛假購買而是直接破解改動遊戲數值的情況並沒有什麼卵用。