Unity3d发布IOS(包含u3d自带IAP内购)的流程-小白篇(三)-u3d配置ios内购部分
上一篇地址:https://www.cnblogs.com/yzxhz/p/9572272.html
在上一篇说道ios内购网页端的配置,配置好证书、appID、配置文件以及真机调试设备后,
这篇文章还需要用到:内购产品的ID,内购产品的公共秘钥。
接下来就进入u3d操作环节。
u3d可以与ios的代码进行相互调用,这样就可以用oc代码编写ios内购,然后再从unity调用,方法网上有,不过对于我这种看不懂oc的菜鸟来说,实在是为难我了。。。不过好在unity提供了一个IAP插件,解决了这个大问题。
首先,想办法获取这个插件,可以去Windows=>Generel=> Asset Store 里面搜索 “Unity IAP”,图中第一个免费的就是。
或者也可以在Windows=>Generel=> Services
里面找到上图红圈部分(注意后面这里,无论是插件在方法1找到还是2找到的,都要把上图这里的状态改为ON),点进去后
这里没有导入的话显示的是Import,我已近导入了,就显示的是Update,点击导入即可。
上述两种方法都要把Services的内购状态改为ON,还有都要登录账号才能操作。
接下来,上代码:
using System; using System.Collections; using System.Collections.Generic; using System.Net; using System.Text; using LitJson; using UnityEngine; using UnityEngine.Purchasing; using UnityEngine.Networking; namespace TianBoWang.Function { [Serializable] public class Products { public string id; public int productType; } /// <summary> /// 购买管理 /// </summary> public class PurchaseManager : MonoBehaviour, IStoreListener { public List<Products> products = new List<Products>(); public string publicKey; ConfigurationBuilder builder; private IStoreController m_Controller; private IAppleExtensions m_AppleExtensions; private int productIndex; private static bool isInited = false; private bool isInitFailed = false; void Awake() { if (!isInited) { InitPurchase(); } } /// <summary> /// 初始化 /// </summary> void InitPurchase() { var module = StandardPurchasingModule.Instance(); builder = ConfigurationBuilder.Instance(module); for (int i = 0; i < products.Count; i++) { builder.AddProduct(products[i].id, (ProductType)products[i].productType); } UnityPurchasing.Initialize(this, builder); } /// <summary> /// 初始化成功 /// </summary> /// <param name="controller">Controller.</param> /// <param name="extensions">Extensions.</param> public void OnInitialized(IStoreController controller, IExtensionProvider extensions) { m_Controller = controller; m_AppleExtensions = extensions.GetExtension<IAppleExtensions>(); m_AppleExtensions.RegisterPurchaseDeferredListener(OnDeferred); isInited = true; } /// <summary> /// iOS 网络延迟错误 /// </summary> /// <param name="item">Item.</param> private void OnDeferred(Product item) { // Debug.Log("网络连接不稳"); } /// <summary> /// 初始化失败 /// </summary> /// <param name="error">Error.</param> public void OnInitializeFailed(InitializationFailureReason error) { isInitFailed = true; Debug.Log("IAPInitializeFailed!!!" + "Reason:" + error); } /// <summary> /// 恢复购买 /// </summary> public void RestorePurchases() { if (Application.platform == RuntimePlatform.IPhonePlayer || Application.platform == RuntimePlatform.OSXPlayer) { if (!isInited) { //loading.SetActive(false); InitPurchase(); } StartCoroutine("InitAndRestore"); } } IEnumerator InitAndRestore() { if (isInitFailed || !isInited) { //初始化失败 StopCoroutine("InitAndRestore"); } yield return new WaitUntil(() => { return m_Controller != null && m_AppleExtensions != null; }); m_AppleExtensions.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."); if (result) { //产品已经restore,不过官方的解释是恢复过程成功了,并不代表所购买的物品都恢复了 } else { // 恢复失败 } StopCoroutine("InitAndRestore"); }); } /// <summary> /// 购买产品 购买的第几个 按钮点击 /// </summary> /// <param name="index">Index.</param> public void OnPurchaseClicked(int index) { if (Application.platform == RuntimePlatform.IPhonePlayer || Application.platform == RuntimePlatform.OSXPlayer) { if (!isInited) InitPurchase(); StartCoroutine("InitAndPurchase", index); } } IEnumerator InitAndPurchase(int index) { if (isInitFailed || !isInited) { //初始化失败 StopCoroutine("InitAndPurchase"); } yield return new WaitUntil(() => { return m_Controller != null && m_AppleExtensions != null; }); m_Controller.InitiatePurchase(products[index].id); StopCoroutine("InitAndPurchase"); } /// <summary> /// 购买成功回调 /// </summary> /// <returns>The purchase.</returns> /// <param name="e">E.</param> public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs e) { //使用id判断是否是当前购买的产品,我这里只有一个产品,所以就是products[0] if (e.purchasedProduct.definition.id == products[0].id) { string transactionReceipt = m_AppleExtensions.GetTransactionReceiptForProduct(e.purchasedProduct); StartCoroutine("CheckRecipe", transactionReceipt);//使用苹果的服务器进行验证订单是否有效 } return PurchaseProcessingResult.Complete; } public void OnPurchaseFailed(Product i, PurchaseFailureReason p) { //购买失败的逻辑 } HttpWebRequest request; IEnumerator CheckRecipe(string s) { JsonData json = new JsonData(); json["receipt-data"] = s; json["password"] = publicKey; Uri urlReal = new Uri("https://buy.itunes.apple.com/verifyReceipt");//正式验证网址 // Uri urlSandBox = new Uri("https://sandbox.itunes.apple.com/verifyReceipt");//沙箱测试验证网址 using (UnityWebRequest www = new UnityWebRequest(urlReal, "POST")) { byte[] postBytes = Encoding.UTF8.GetBytes(json.ToJson()); www.uploadHandler = (UploadHandler)new UploadHandlerRaw(postBytes); www.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer(); www.SetRequestHeader("Content-Type", "application/json"); www.timeout = 20;//20秒后超时 yield return www.Send(); if (www.isNetworkError) { //Debug.Log("网络错误:"+www.error); } else { if (www.responseCode == 200) { JsonData resoultJson = JsonMapper.ToObject(www.downloadHandler.text); if (resoultJson["status"].ToString() == "0") { //验证成功的逻辑 } else { //验证失败的逻辑 } } } } StopCoroutine("CheckRecipe"); } } }
2018/12/21补充:
m_Controller.products.WithID(/*<Product>.id*/).metadata.localizedPriceString;
使用这种方法获取iap后台的价格以及货币符号
使用方法:我这里使用的是LitJson插件, 将代码挂到任意物体
有几个需要购买的产品就在size里面写几。
id就是每个要购买的产品的id(苹果后台获取)
product Type代表类型,(0表示消耗品,1表示费消耗品,2表示订阅)
下面的public key是苹果后台产品的公共秘钥
然后在按钮点击的时候调用其中的购买方法( void OnPurchaseClicked(int i) ) 参数i代表在面板上加的第几个产品、,以及恢复购买方法( void RestorePurchases())即可。
其中有几处需要自己写逻辑的地方,我已经代码注释标明了,例如购买成功、失败之后要执行的逻辑等。
在二次验证的时候有一些坑:
1.二次验证向服务器发送不能使用WWW通讯。
2.不要使用C#的HttpWebRequest,这个鬼东西当你在协程中使用的时候,网络不好就会出现程序假死!要使用unity内置的 UnityWebRequest。
3.测试的时候用沙箱验证网址测试,送审的时候别忘了使用正式验证网址,不然被打回。
在二次验证中返回的正确/错误代码以及意思:
0 | 验证成功 |
21000 | App Store不能读取你提供的JSON对象 |
21002 | receipt-data域的数据有问题 |
21003 | receipt无法通过验证 |
21004 | 提供的shared secret不匹配你账号中的shared secret |
21005 | receipt服务器当前不可用 |
21006 | receipt合法,但是订阅已过期。服务器接收到这个状态码时,receipt数据仍然会解码并一起发送 |
21007 | receipt是Sandbox receipt,但却发送至生产系统的验证服务 |
21008 | receipt是生产receipt,但却发送至Sandbox环境的验证服务 |