364 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C#
		
	
	
	
			
		
		
	
	
			364 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C#
		
	
	
	
| #if USE_IAP
 | ||
| using System;
 | ||
| using System.Collections.Generic;
 | ||
| using UnityEngine;
 | ||
| using UnityEngine.Purchasing;
 | ||
| 
 | ||
| public class IAPTool : NormalSingleton<IAPTool>, IStoreListener
 | ||
| {
 | ||
| 	public event Action OnPurchaseBegin;
 | ||
| 	public event Action<string, bool> OnPurchaseDone;
 | ||
| 	public event Action<bool> OnRestoreDone;
 | ||
| 
 | ||
| 	public bool IsRestoring => mIsRestoring;
 | ||
| 	private bool mIsRestoring = false;
 | ||
| 
 | ||
| 	private static IStoreController m_StoreController; // 存储商品信息;
 | ||
| 	private static IExtensionProvider m_StoreExtensionProvider; // IAP扩展工具;
 | ||
| 	private bool m_PurchaseInProgress = false; // 是否处于付费中;
 | ||
| 
 | ||
| 	private Dictionary<string, ProductType> mInitProductDic;
 | ||
| 	private SubscriptionInfo mSubsInfo = null;
 | ||
| 
 | ||
| 	public void Initialize()
 | ||
| 	{
 | ||
| 		if (m_StoreController == null && m_StoreExtensionProvider == null)
 | ||
| 			InitUnityPurchase();
 | ||
| 	}
 | ||
| 
 | ||
| 	private bool IsInitialized()
 | ||
| 	{
 | ||
| 		return m_StoreController != null && m_StoreExtensionProvider != null;
 | ||
| 	}
 | ||
| 
 | ||
| 	public void AddProducts(Dictionary<string, ProductType> pInitProductDic)
 | ||
| 	{
 | ||
| 		mInitProductDic = pInitProductDic;
 | ||
| 	}
 | ||
| 
 | ||
| 	// 初始化IAP;
 | ||
| 	private void InitUnityPurchase()
 | ||
| 	{
 | ||
| 		if (IsInitialized()) return;
 | ||
| 
 | ||
| 		// 标准采购模块;
 | ||
| 		StandardPurchasingModule module = StandardPurchasingModule.Instance();
 | ||
| 
 | ||
| 		// 配置模式;
 | ||
| 		ConfigurationBuilder builder = ConfigurationBuilder.Instance(module);
 | ||
| 
 | ||
| 		// 注意ProductType的类型,Consumable是可以无限购买(比如水晶),NonConsumable是只能购买一次(比如关卡),Subscription是每月订阅(比如VIP);
 | ||
| 		// 这里初始化没有添加平台信息,因为平台信息有的时候还存在bug,如果必须添加,也可以添加,没有问题,确保平台信息添加正确就行了。
 | ||
| 
 | ||
| 		foreach (string tID in IAPProducts.ProductDic.Keys)
 | ||
| 		{
 | ||
| 			builder.AddProduct(tID, IAPProducts.ProductDic[tID]);
 | ||
| 		}
 | ||
| 
 | ||
| 		if (mInitProductDic != null && mInitProductDic.Count > 0)
 | ||
| 		{
 | ||
| 			foreach (string tID in mInitProductDic.Keys)
 | ||
| 			{
 | ||
| 				builder.AddProduct(tID, mInitProductDic[tID]);
 | ||
| 			}
 | ||
| 		}
 | ||
| 		
 | ||
| 		//初始化;
 | ||
| 		UnityPurchasing.Initialize(this, builder);
 | ||
| 	}
 | ||
| 
 | ||
| #region Public Func
 | ||
| 	// 根据ID给购买商品;
 | ||
| 	public void BuyProductByID(string productId)
 | ||
| 	{
 | ||
| 		if (IsInitialized())
 | ||
| 		{
 | ||
| 			if (m_PurchaseInProgress == true) return;
 | ||
| 
 | ||
| 			Product product = m_StoreController.products.WithID(productId);
 | ||
| 			if (product != null && product.availableToPurchase)
 | ||
| 			{
 | ||
| 				OnPurchaseBegin?.Invoke();
 | ||
| 				m_PurchaseInProgress = true;
 | ||
| 				Debug.Log(string.Format("Purchasing product asychronously: '{0}'", product.definition.id));
 | ||
| 				m_StoreController.InitiatePurchase(product);
 | ||
| 			}
 | ||
| 			else
 | ||
| 			{
 | ||
| 				Debug.Log("BuyProductID: FAIL. Not purchasing product, either is not found or is not available for purchase");
 | ||
| 			}
 | ||
| 		}
 | ||
| 		else
 | ||
| 		{
 | ||
| 			Debug.Log("BuyProductID FAIL. Not initialized.");
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	// 确认购买产品成功;
 | ||
| 	public void DoConfirmPendingPurchaseByID(string productId)
 | ||
| 	{
 | ||
| 		Product product = m_StoreController.products.WithID(productId);
 | ||
| 		if (product != null && product.availableToPurchase)
 | ||
| 		{
 | ||
| 			if (m_PurchaseInProgress)
 | ||
| 			{
 | ||
| 				m_StoreController.ConfirmPendingPurchase(product);
 | ||
| 				m_PurchaseInProgress = false;
 | ||
| 			}
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	// 恢复购买;
 | ||
| 	public void RestorePurchases()
 | ||
| 	{
 | ||
| 		if (!IsInitialized())
 | ||
| 		{
 | ||
| 			OnRestoreDone?.Invoke(false);
 | ||
| 			Debug.Log("RestorePurchases FAIL. Not initialized.");
 | ||
| 			return;
 | ||
| 		}
 | ||
| 		if (Application.platform == RuntimePlatform.IPhonePlayer ||
 | ||
| 			Application.platform == RuntimePlatform.OSXPlayer)
 | ||
| 		{
 | ||
| 			Debug.Log("RestorePurchases started ...");
 | ||
| 			mIsRestoring = true;
 | ||
| 			var apple = m_StoreExtensionProvider.GetExtension<IAppleExtensions>();
 | ||
| 			apple.RestoreTransactions((result) => {
 | ||
| 				mIsRestoring = false;
 | ||
| 				OnRestoreDone?.Invoke(result);
 | ||
| 				// 返回一个bool值,如果成功,则会多次调用支付回调,然后根据支付回调中的参数得到商品id,最后做处理(ProcessPurchase); 
 | ||
| 				Debug.Log("RestorePurchases continuing: " + result + ". If no further messages, no purchases available to restore.");
 | ||
| 			});
 | ||
| 		}
 | ||
| 		else
 | ||
| 		{
 | ||
| 			Debug.Log("RestorePurchases FAIL. Not supported on this platform. Current = " + Application.platform);
 | ||
| 		}
 | ||
| 	}
 | ||
| #endregion
 | ||
| 
 | ||
| #region IStoreListener Callback
 | ||
| 	// IAP初始化成功回掉函数;
 | ||
| 	public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
 | ||
| 	{
 | ||
| 		Debug.Log("IAP initialize Succeed!");
 | ||
| 
 | ||
| 		m_StoreController = controller;
 | ||
| 		m_StoreExtensionProvider = extensions;
 | ||
| 
 | ||
| 		// 这里可以获取您在AppStore和Google Play 上配置的商品;
 | ||
| 		ProductCollection products = m_StoreController.products;
 | ||
| 		Product[] all = products.all;
 | ||
| 		for (int i = 0; i < all.Length; i++)
 | ||
| 		{
 | ||
| 			Debug.Log(all[i].metadata.localizedTitle + "|" + all[i].metadata.localizedPriceString + "|" + all[i].metadata.localizedDescription + "|" + all[i].metadata.isoCurrencyCode);
 | ||
| 		}
 | ||
| 
 | ||
| #if UNITY_IOS
 | ||
| 		m_StoreExtensionProvider.GetExtension<IAppleExtensions>().RegisterPurchaseDeferredListener(OnDeferred);
 | ||
| #endif
 | ||
| 
 | ||
| /*
 | ||
| 		Dictionary<string, string> introductory_info_dict = null;
 | ||
| 
 | ||
| #if UNITY_IOS
 | ||
| 		introductory_info_dict = m_StoreExtensionProvider.GetExtension<IAppleExtensions>().GetIntroductoryPriceDictionary();
 | ||
| #endif
 | ||
| 
 | ||
| 		Debug.Log("IAP - Available items:");
 | ||
| 		foreach (var item in controller.products.all)
 | ||
| 		{
 | ||
| 			if (item.availableToPurchase)
 | ||
| 			{
 | ||
| 				Debug.Log("IAP - " + string.Join(" - ",
 | ||
| 					new[]
 | ||
| 					{
 | ||
| 						item.metadata.localizedTitle,
 | ||
| 						item.metadata.localizedDescription,
 | ||
| 						item.metadata.isoCurrencyCode,
 | ||
| 						item.metadata.localizedPrice.ToString(),
 | ||
| 						item.metadata.localizedPriceString,
 | ||
| 						item.transactionID,
 | ||
| 						item.receipt
 | ||
| 					}));
 | ||
| 
 | ||
|                 // this is the usage of SubscriptionManager class
 | ||
|                 if (item.receipt != null)
 | ||
| 				{
 | ||
|                     if (item.definition.type == ProductType.Subscription)
 | ||
| 					{
 | ||
|                         if (CheckIfProductIsAvailableForSubscriptionManagerC(item.receipt))
 | ||
| 						{
 | ||
|                             string intro_json = (introductory_info_dict == null || !introductory_info_dict.ContainsKey(item.definition.storeSpecificId)) ? null : introductory_info_dict[item.definition.storeSpecificId];
 | ||
|                             SubscriptionManager p = new SubscriptionManager(item, intro_json);
 | ||
|                             SubscriptionInfo info = p.getSubscriptionInfo();
 | ||
| 							mSubsInfo = info;
 | ||
|                             Debug.Log("product id is: " + info.getProductId());
 | ||
|                             Debug.Log("purchase date is: " + info.getPurchaseDate());
 | ||
|                             Debug.Log("subscription next billing date is: " + info.getExpireDate());
 | ||
|                             Debug.Log("is subscribed? " + info.isSubscribed().ToString());
 | ||
|                             Debug.Log("is expired? " + info.isExpired().ToString());
 | ||
|                             Debug.Log("is cancelled? " + info.isCancelled());
 | ||
|                             Debug.Log("product is in free trial peroid? " + info.isFreeTrial());
 | ||
|                             Debug.Log("product is auto renewing? " + info.isAutoRenewing());
 | ||
|                             Debug.Log("subscription remaining valid time until next billing date is: " + info.getRemainingTime());
 | ||
|                             Debug.Log("is this product in introductory price period? " + info.isIntroductoryPricePeriod());
 | ||
|                             Debug.Log("the product introductory localized price is: " + info.getIntroductoryPrice());
 | ||
|                             Debug.Log("the product introductory price period is: " + info.getIntroductoryPricePeriod());
 | ||
|                             Debug.Log("the number of product introductory price period cycles is: " + info.getIntroductoryPricePeriodCycles());
 | ||
|                         }
 | ||
| 						else
 | ||
| 						{
 | ||
|                             Debug.Log("This product is not available for SubscriptionManager class, only products that are purchase by 1.19+ SDK can use this class.");
 | ||
|                         }
 | ||
|                     }
 | ||
| 					else
 | ||
| 					{
 | ||
|                         Debug.Log("the product is not a subscription product");
 | ||
|                     }
 | ||
|                 }
 | ||
| 				else
 | ||
| 				{
 | ||
|                     Debug.Log("the product should have a valid receipt");
 | ||
|                 }
 | ||
| 			}
 | ||
| 		}
 | ||
| */
 | ||
| 	}
 | ||
| 
 | ||
| 	// IAP初始化失败回掉函数(没有网络的情况下并不会调起,而是一直等到有网络连接再尝试初始化);
 | ||
| 	public void OnInitializeFailed(InitializationFailureReason error)
 | ||
| 	{
 | ||
| 		switch (error)
 | ||
| 		{
 | ||
| 			case InitializationFailureReason.AppNotKnown:
 | ||
| 				Debug.LogError("Is your App correctly uploaded on the relevant publisher console?");
 | ||
| 				break;
 | ||
| 			case InitializationFailureReason.PurchasingUnavailable:
 | ||
| 				Debug.Log("Billing disabled! Ask the user if billing is disabled in device settings.");
 | ||
| 				break;
 | ||
| 			case InitializationFailureReason.NoProductsAvailable:
 | ||
| 				Debug.Log("No products available for purchase! Developer configuration error; check product metadata!");
 | ||
| 				break;
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
|     private bool CheckIfProductIsAvailableForSubscriptionManagerC(string receipt)
 | ||
| 	{
 | ||
|         var receipt_wrapper = (Dictionary<string, object>)MiniJson.JsonDecode(receipt);
 | ||
|         if (!receipt_wrapper.ContainsKey("Store") || !receipt_wrapper.ContainsKey("Payload"))
 | ||
| 		{
 | ||
|             Debug.Log("The product receipt does not contain enough information");
 | ||
|             return false;
 | ||
|         }
 | ||
|         var store = (string)receipt_wrapper ["Store"];
 | ||
|         var payload = (string)receipt_wrapper ["Payload"];
 | ||
| 
 | ||
|         if (payload != null )
 | ||
| 		{
 | ||
|             switch (store)
 | ||
| 			{
 | ||
|             case GooglePlay.Name:
 | ||
|                 {
 | ||
|                     var payload_wrapper = (Dictionary<string, object>)MiniJson.JsonDecode(payload);
 | ||
|                     if (!payload_wrapper.ContainsKey("json")) {
 | ||
|                         Debug.Log("The product receipt does not contain enough information, the 'json' field is missing");
 | ||
|                         return false;
 | ||
|                     }
 | ||
|                     var original_json_payload_wrapper = (Dictionary<string, object>)MiniJson.JsonDecode((string)payload_wrapper["json"]);
 | ||
|                     if (original_json_payload_wrapper == null || !original_json_payload_wrapper.ContainsKey("developerPayload")) {
 | ||
|                         Debug.Log("The product receipt does not contain enough information, the 'developerPayload' field is missing");
 | ||
|                         return false;
 | ||
|                     }
 | ||
|                     var developerPayloadJSON = (string)original_json_payload_wrapper["developerPayload"];
 | ||
|                     var developerPayload_wrapper = (Dictionary<string, object>)MiniJson.JsonDecode(developerPayloadJSON);
 | ||
|                     if (developerPayload_wrapper == null || !developerPayload_wrapper.ContainsKey("is_free_trial") || !developerPayload_wrapper.ContainsKey("has_introductory_price_trial")) {
 | ||
|                         Debug.Log("The product receipt does not contain enough information, the product is not purchased using 1.19 or later");
 | ||
|                         return false;
 | ||
|                     }
 | ||
|                     return true;
 | ||
|                 }
 | ||
|             case AppleAppStore.Name:
 | ||
|             case AmazonApps.Name:
 | ||
|             case MacAppStore.Name:
 | ||
|                 {
 | ||
|                     return true;
 | ||
|                 }
 | ||
|             default:
 | ||
|                 {
 | ||
|                     return false;
 | ||
|                 }
 | ||
|             }
 | ||
|         }
 | ||
|         return false;
 | ||
|     }
 | ||
| 
 | ||
| 	// 支付成功处理函数;
 | ||
| 	public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs e)
 | ||
| 	{
 | ||
| 		m_PurchaseInProgress = false;
 | ||
| 
 | ||
| 		Debug.Log("Purchase OK: " + e.purchasedProduct.definition.id);
 | ||
| 
 | ||
| 		// 消息结构 : Receipt: {"Store":"fake","TransactionID":"9c5c16a5-1ae4-468f-806d-bc709440448a","Payload":"{ \"this\" : \"is a fake receipt\" }"};
 | ||
| 		Debug.Log("Receipt: " + e.purchasedProduct.receipt);
 | ||
| 
 | ||
| 		OnPurchaseDone?.Invoke(e.purchasedProduct.definition.id, true);
 | ||
| 
 | ||
| 		// 我们自己后台完毕的话,通过代码设置成功(如果是不需要后台设置直接设置完毕,不要设置Pending);
 | ||
| 		return PurchaseProcessingResult.Complete;
 | ||
| 	}
 | ||
| 
 | ||
| 	// 支付失败回掉函数;
 | ||
| 	public void OnPurchaseFailed(Product item, PurchaseFailureReason r)
 | ||
| 	{
 | ||
| 		Debug.Log("Purchase OK: " + item.definition.id);
 | ||
| 		m_PurchaseInProgress = false;
 | ||
| 		OnPurchaseDone?.Invoke(item.definition.id, false);
 | ||
| 	}
 | ||
| 
 | ||
| 	// 购买延迟提示(这个看自己项目情况是否处理);
 | ||
| 	public void OnDeferred(Product item)
 | ||
| 	{
 | ||
| 		Debug.Log("Purchase deferred: " + item.definition.id);
 | ||
| 		OnPurchaseDone?.Invoke(item.definition.id, false);
 | ||
| 	}
 | ||
| 
 | ||
| 	// 恢复购买功能执行回掉函数;
 | ||
| 	public void OnTransactionsRestored(bool success)
 | ||
| 	{
 | ||
| 		Debug.Log("Transactions restored : " + success);
 | ||
| 	}
 | ||
| #endregion
 | ||
| 
 | ||
| #region custom functions
 | ||
| 	public string GetPriceByID(string pID)
 | ||
| 	{
 | ||
| 		if (m_StoreController == null && m_StoreExtensionProvider == null)
 | ||
| 			return "";
 | ||
| 
 | ||
| 		Product[] tProducts = m_StoreController.products.all;
 | ||
| 		for (int i = 0; i < tProducts.Length; i++)
 | ||
| 		{
 | ||
| 			Debug.Log(tProducts[i].metadata.localizedTitle + "|" + tProducts[i].metadata.localizedPriceString + "|" + tProducts[i].metadata.localizedDescription + "|" + tProducts[i].metadata.isoCurrencyCode);
 | ||
| 			Product tItem = tProducts[i];
 | ||
| 			if (tItem.definition.id.Equals(pID))
 | ||
| 			{
 | ||
| #if UNITY_ANDROID
 | ||
| 				return tItem.metadata.GetGoogleProductMetadata().localizedPriceString;
 | ||
| #else
 | ||
| 				return tItem.metadata.localizedPriceString;
 | ||
| #endif
 | ||
| 			}
 | ||
| 		}
 | ||
| 
 | ||
| 		return "";
 | ||
| 	}
 | ||
| 
 | ||
| 	public SubscriptionInfo GetSubscriptionInfo()
 | ||
| 	{
 | ||
| 		return mSubsInfo;
 | ||
| 	}
 | ||
| #endregion
 | ||
| }
 | ||
| #endif |