赞
踩
1、MacBook Pro,Apple M1 Pro,macOS Sonoma 14.3.1
2、Unity Hub 版本3.7.0(3.7.0)
3、unity Version 2020.3.28f1 Personal
4、In App Purchasing Package v4.1.5
1、根据使用的unity IDE版本选择对应的开发文档,该链接为unity 2020.3.28f1的IAP开发文档
1)在该文档的左上角可以选择不同Unity 版本对应的开发文档,选择你所需要的即可:
2、根据你安装的In App Purchasing版本选择对应的开发文档,该链接为 4.1.5版本的开发文档
3、在 Apple Store Connect 中创建App和商品ID,并保存 Bundle Identifier 和 商品ID,在初始化App服务时会用到。
1、添加IAP Package
2、打开IAP Service服务
sing System; using System.Collections.Generic; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using NtUtils; using UnityEngine; using UnityEngine.Purchasing; namespace NtSDK { public class NtIAPManager : IStoreListener { private const string Tag = "[NtIAPManager]"; private IStoreController m_Controller; private IAppleExtensions m_AppleExtensions; private SkuDetailCallback m_SkuDetailCallback; private NtIAPCallback m_IapCallback; private string[] m_IapIdList; private string m_ErrorMsg = NtCommonInstance<NtLocalizationManager>.Instance.GetValueByKey( "IDS_ERROR_CODE_-1"); // 获取商品详情 // 获取商品详情的本质就是利用商品ID初始化Unity IAP服务的,商品ID必须是真实有效的 internal void GetSkuDetails(String iapIDs, SkuDetailCallback skuDetailCallback) { NtLog.Log(NtLog.NtLogLevel.Log, Tag, "start get sku details"); m_SkuDetailCallback = skuDetailCallback; if (string.IsNullOrEmpty(iapIDs)) { NtLog.Log(NtLog.NtLogLevel.Error, Tag, "start get sku details fail, iapIDs is nil"); skuDetailCallback?.onFailed(NtErrorCode.Failed, m_ErrorMsg); return; } string[] iapIdList = iapIDs.Split(';'); m_IapIdList = iapIdList; InitUnityIAP(false); } internal void IAPPay(NtIAPPay payInfo, NtIAPCallback iapCallback) { m_IapCallback = iapCallback; var productID = payInfo.productId; if (m_Controller == null) { NtLog.Log(NtLog.NtLogLevel.Error, Tag, "iap pay fail, m_Controller is nil"); OnFailCallback(productID); return; } Product product = m_Controller.products.WithID(productID); if (product == null || !product.availableToPurchase) { NtLog.Log(NtLog.NtLogLevel.Error, Tag, "iap pay fail, product is not available"); OnFailCallback(productID); return; } var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance()); bool canMakePayments = builder.Configure<IAppleConfiguration>().canMakePayments; if (!canMakePayments) { NtLog.Log(NtLog.NtLogLevel.Error, Tag, "iap pay fail, user can not make payments"); OnFailCallback(productID); return; } // 这里根据实际的业务需求:请求服务端接口创建订单 } // 初始化Unity IAP服务 private void InitUnityIAP(bool isClearFailedOrder) { NtLog.Log(NtLog.NtLogLevel.Log, Tag, "start init unity iAP"); if (m_IapIdList == null || m_IapIdList.Length == 0) { NtLog.Log(NtLog.NtLogLevel.Error, Tag, "init unity iAP, iapId is nil"); OnFailCallback("", NtErrorCode.Failed, m_ErrorMsg); return; } if (Application.internetReachability == NetworkReachability.NotReachable) { NtLog.Log(NtLog.NtLogLevel.Warning, Tag, "没有网络,IAP会一直初始化"); } m_IsClearFailedOrder = isClearFailedOrder; var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance()); foreach (string item in m_IapIdList) { // 如果只做单平台,比如Mac OS就不需要像官方文档一样传入platform类型 builder.AddProduct(item, ProductType.Consumable); } UnityPurchasing.Initialize(this, builder); } private void OnFailCallback(string productID, int code = NtErrorCode.Failed, string msg = null) { if (!string.IsNullOrEmpty(msg)) { NtPromptBox.ShowNtPromptBoxContent(msg); } m_IapCallback?.onPayFail(code, productID); } #region IStoreListener /// <summary> /// This will be called when Unity IAP has finished initialising. /// </summary> public void OnInitialized(IStoreController controller, IExtensionProvider extensions) { var logTip = m_IsClearFailedOrder ? "and may need clear failed order" : "and need get sku details"; NtLog.Log(NtLog.NtLogLevel.Log, Tag, "IAP initialize success " + logTip); m_Controller = controller; m_AppleExtensions = extensions.GetExtension<IAppleExtensions>(); m_AppleExtensions.RegisterPurchaseDeferredListener(OnDeferred); if (m_IsClearFailedOrder) { return; } List<SkuDetailsInfo> cpSkuDetailsInfos = new List<SkuDetailsInfo>(); foreach (var product in m_Controller.products.all) { SkuDetailsInfo skuDetailsInfo = new SkuDetailsInfo(); skuDetailsInfo.skuType = product.definition.type.ToString(); // 商品类型 skuDetailsInfo.productId = product.definition.id; // 商品ID skuDetailsInfo.productDescription = product.metadata.localizedDescription; // 商品的本地化描述。 skuDetailsInfo.productName = product.metadata.localizedTitle; // 面向消费者的商品名称,用于应用商店 UI。 skuDetailsInfo.price = product.metadata.localizedPriceString; // 面向消费者的价格字符串,包括货币符号,用于应用商店 UI。 skuDetailsInfo.priceAmount = $"{product.metadata.localizedPrice}"; // 内部系统的商品价格值。 skuDetailsInfo.currencyCode = product.metadata.isoCurrencyCode; // 商品本地化货币的 ISO 代码。 cpSkuDetailsInfos.Add(skuDetailsInfo); NtLog.Log(NtLog.NtLogLevel.Debug, Tag, $"productId:{skuDetailsInfo.productId},skuType:{product.definition.type.ToString()},productDescription:{skuDetailsInfo.productDescription},productName:{skuDetailsInfo.productName},price:{skuDetailsInfo.price},priceAmount:{skuDetailsInfo.priceAmount},currencyCode:{skuDetailsInfo.currencyCode}"); if (!product.availableToPurchase) { NtLog.Log(NtLog.NtLogLevel.Warning, Tag, $"{product.definition.id} is not purchased"); } } m_SkuDetailCallback?.onSuccess(cpSkuDetailsInfos); } /// <summary> /// Called when Unity IAP encounters an unrecoverable initialization error. /// /// Note that this will not be called if Internet is unavailable; Unity IAP /// will attempt initialization until it becomes available. /// </summary> public void OnInitializeFailed(InitializationFailureReason error) { if (m_IsClearFailedOrder) { NtLog.Log(NtLog.NtLogLevel.Log, Tag, "sdk init iap fail when clear failed order, reason is " + error.ToString()); return; } NtLog.Log(NtLog.NtLogLevel.Error, Tag, "IAP initialized fail, reason is " + error.ToString()); m_SkuDetailCallback?.onFailed(NtErrorCode.Failed, error.ToString()); } /// <summary> /// This will be called when a purchase completes. /// </summary> public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs purchaseEvent) { NtLog.Log(NtLog.NtLogLevel.Log, Tag, "purchased successfully"); string productID = purchaseEvent.purchasedProduct.definition.id; string productName = purchaseEvent.purchasedProduct.metadata.localizedTitle; string transactionID = purchaseEvent.purchasedProduct.transactionID; string finalReceipt = purchaseEvent.purchasedProduct.receipt; // 这里需要向服务端校验支付凭据的正确性,只有校验成功才可以结束交易 if (!string.IsNullOrEmpty(finalReceipt)) { // 这里需要向服务端校验支付凭据的正确性,只有校验成功才可以结束交易 // 此时标记为pending,当交易凭证被服务端校验成功后再确认购买成功 return PurchaseProcessingResult.Pending; } return PurchaseProcessingResult.Complete; } /// <summary> /// Called when a purchase fails. /// </summary> public void OnPurchaseFailed(Product product, PurchaseFailureReason failureReason) { NtLog.Log(NtLog.NtLogLevel.Error, Tag, $"IAP purchase fail, productId is {product.definition.id}, transactionID is {product.transactionID}, reason is {failureReason.ToString()}"); } /// <summary> /// iOS Specific. /// This is called as part of Apple's 'Ask to buy' functionality, /// when a purchase is requested by a minor and referred to a parent /// for approval. /// /// When the purchase is approved or rejected, the normal purchase events /// will fire. /// </summary> /// <param name="item">Item.</param> private void OnDeferred(Product item) { NtLog.Log(NtLog.NtLogLevel.Warning, Tag, "Purchase deferred: " + item.definition.id); } #endregion } }
1、在实际测试中发现,当Unity应用失去焦点后,就接收不到Unity 支付成功的回调。
2、Unity IAP 测试不能连接Unity Editor测试,必须要打包才行。
3、商品1支付完成,但没有调用结束交易接口;此时无法继续购买商品1,但不影响购买其他商品。
4、Unity IAP 没有像iOS IAP一样提供我们类似交易队列的东西,我们只能被动接受Unity IAP给我们的支付回调。
5、如果使用无效的包名,Unity IAP服务就会初始化失败。
6、如果初始化IAP所携带的商品ID都不是对应包名的,IAP服务就会初始化失败,错误原因:NoProductsAvailable;
7、如果支付时的商品ID,没有被包含在初始化IAP服务的参数中,会支付报错
8、测试Unity IAP时,和iOS IAP流程基本一致。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。