Unity3D-5.5版本的IAP-IOS使用方式(In-App-Purchase)

60 篇文章 3 订阅
40 篇文章 0 订阅

参考:http://blog.csdn.net/pz789as/article/details/70208867

 

关于IAP的设置问题,网上其实已经写了很多了,我也不多赘述,那么我在这里只写一些细节,特别是对于新手来说的那种。

第一步肯定是去iTunes Connect里面添加项目,并且设置商品:

现在苹果其实已经不需要你上传ipa包了,只要设置好了相关信息既可以测试iap功能。

需要注意的是:“协议、税务和银行业务” 填写,这是最最重要的,如果没有填写,你永远不会收到结果,但是也不报错。

税务信息如果填写完毕,那么应该是这样的:

也就是后面可以download的

在 功能 里面设置商品,你需要上传截图,不然是不会显示的,设置好之后,它们的状态应该是“准备提交”状态,说明这一步已经好了。然后还需要在APP Store信息中的初始版本里面选择你刚刚设置的商品,结果如下:

 

另一个是沙盒测试账号,这个必须要使用你在iTunes Connect人员配置中设置的测试账号,否则是无法测试的。而且,在测试之前,一定要把设置里面的“iTunes Store与 App Store”退出。然后在游戏中点击购买的时候,会弹出一个登录账号的框,选择已经存在的账号,填写账号密码即可开始测试了。

这是需要注意的三个重要点

 

接下来就是unity这边了

在unity5.5版本以后,你需要在创建项目的时候打开unity服务,不然后面去弄,比较麻烦,半天连不上。

这个unity服务中,你找到In-App Purchasing,将他开启,然后import Unity的内购插件。

这里需要说明的一点是关于Restore的使用方式,其实Unity已经做好了,不需要关心,我要说的是他的返回的方式。

因为之前不知道,在看到Restore之后,返回只有一个bool类型的变量,所以不知道用户到底买了哪个非消费类型的商品。

其实,Unity在返回bool类型之前,其实还是会回调购买成功的函数,然后参数带了商品的ID,如果有多个非消费类型商品,它会多次回调,这样你就可以根据ID去做你的处理了。

对于其他的操作,unity的文档和案例已经写的很清楚了。

另外,使用代码和使用IAPButton是一样的效果,我贴出使用代码的方法:

 

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

namespace IAPCustom
{
	//其实直接使用插件提供的IAPButton即可,不需要关心其他的
public class UnityPurchaser : MonoBehaviour, IStoreListener {

	private static IStoreController m_StoreController;
	private static IExtensionProvider m_StoreExtensionProvider;
	public static string kProductIDConsumable = "buycoins0";
	public static string kProductIDNonConsumable = "removeads";
	public static string kProductIDWeapon = "weapon";

	public static string kProductNameAppleConsumable = "com.gjc.wf.buycoins0";
	public static string kProductNameAppleNonConsumable = "com.gjc.wf.buyremoveads";
	public static string kProductNameAppleWeapon = "com.gjc.wf.weapon";

	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 (m_StoreController == null){
			InitializePurchasing();
		}	
	}
	public void InitializePurchasing() 
	{
		if (IsInitialized())
		{
			return;
		}
		ConfigurationBuilder builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());

		// builder.AddProduct(kProductIDConsumable, ProductType.Consumable, new IDs(){
		// 	{kProductNameAppleConsumable, AppleAppStore.Name}
		// });
		// builder.AddProduct(kProductIDNonConsumable, ProductType.NonConsumable, new IDs(){
		// 	{kProductNameAppleNonConsumable, AppleAppStore.Name}
		// });
		// builder.AddProduct(kProductIDSubscription, ProductType.Subscription, new IDs(){
		// 	{ kProductNameAppleSubscription, AppleAppStore.Name },
		// 	{ kProductNameGooglePlaySubscription, GooglePlay.Name },
		// });

		//添加商品
		IDs kProductBuyCoins0 = new IDs();
		IDs kProductRemoveAds = new IDs();
		kProductBuyCoins0.Add(kProductNameAppleConsumable, new string[]{AppleAppStore.Name});
		kProductRemoveAds.Add(kProductNameAppleNonConsumable, new string[]{AppleAppStore.Name});
		builder.AddProduct(kProductIDConsumable, ProductType.Consumable, kProductBuyCoins0);
		builder.AddProduct(kProductIDNonConsumable, ProductType.NonConsumable, kProductRemoveAds);
		builder.AddProduct(kProductIDWeapon, ProductType.NonConsumable, new IDs(){
			{kProductNameAppleWeapon, AppleAppStore.Name}
		});
		// ProductCatalog pc = ProductCatalog.LoadDefaultCatalog();
		
		UnityPurchasing.Initialize(this, builder);
	}
	
	
	private bool IsInitialized()
	{
		return m_StoreController != null && m_StoreExtensionProvider != null;
	}
	public void BuyConsumable()
	{
		BuyProductID(kProductIDConsumable);
	}
	public void BuyNonConsumable()
	{
		BuyProductID(kProductIDNonConsumable);
	}
	public void BuyWeapon(){
		BuyProductID(kProductIDWeapon);
	}
	public void BuySubscription()
	{
		BuyProductID(kProductIDSubscription);
	}
	void BuyProductID(string productId)
	{
		if (IsInitialized())
		{
			Product product = m_StoreController.products.WithID(productId);
			if (product != null && product.availableToPurchase)
			{
				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 RestorePurchases()
	{
		if (!IsInitialized())
		{
			Debug.Log("RestorePurchases FAIL. Not initialized.");
			return;
		}
		if (Application.platform == RuntimePlatform.IPhonePlayer || 
			Application.platform == RuntimePlatform.OSXPlayer)
		{
			Debug.Log("RestorePurchases started ...");
			var apple = m_StoreExtensionProvider.GetExtension<IAppleExtensions>();
			apple.RestoreTransactions((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);
		}
	}

	//  
	// --- IStoreListener
	//
	public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
	{
		//初始化成功
		Debug.Log("OnInitialized: PASS");
		m_StoreController = controller;
		m_StoreExtensionProvider = extensions;
	}
	public void OnInitializeFailed(InitializationFailureReason error)
	{
		//初始化失败
		Debug.Log("OnInitializeFailed InitializationFailureReason:" + error);
	}
	public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args) 
	{
		//根据不同的id,做对应的处理。。
		if (String.Equals(args.purchasedProduct.definition.id, kProductIDConsumable, StringComparison.Ordinal))
		{
			Debug.Log(string.Format("ProcessPurchase: PASS. Product: '{0}'", args.purchasedProduct.definition.id));
		}
		else if (String.Equals(args.purchasedProduct.definition.id, kProductIDNonConsumable, StringComparison.Ordinal))
		{
			Debug.Log(string.Format("ProcessPurchase: PASS. Product: '{0}'", args.purchasedProduct.definition.id));
		}
		else if (String.Equals(args.purchasedProduct.definition.id, kProductIDWeapon, StringComparison.Ordinal)){
			Debug.Log(string.Format("ProcessPurchase: PASS. Product: '{0}'", args.purchasedProduct.definition.id));
		}
		else if (String.Equals(args.purchasedProduct.definition.id, kProductIDSubscription, StringComparison.Ordinal))
		{
			Debug.Log(string.Format("ProcessPurchase: PASS. Product: '{0}'", args.purchasedProduct.definition.id));
		}
		else 
		{
			Debug.Log(string.Format("ProcessPurchase: FAIL. Unrecognized product: '{0}'", args.purchasedProduct.definition.id));
		}
		return PurchaseProcessingResult.Complete;
	}
	
	public void OnPurchaseFailed(Product product, PurchaseFailureReason failureReason)
	{
		//支付失败
		Debug.Log(string.Format("OnPurchaseFailed: FAIL. Product: '{0}', PurchaseFailureReason: {1}", product.definition.storeSpecificId, failureReason));
	}
}
}

 

两个方法使用其中一个即可。。

 

 

下午补充:

在后面的测试中发现,如果商品有非消费类型时,使用UnityIAP插件中自带的IAPButton组件时,会出现很奇怪的情况:

在购买完非消费类型的商品后,然后删除APP,重新安装APP的时候,它会莫名其妙的自动调用Restore,这样等于我还没登录,它就使用原来的账号去获取检测是否购买。我查了半天也不知道哪里出的问题。但是当我不使用IAPButton,全部用自己代码去调用就不会出现这种情况。。

不过没有非消费类型的商品还是可以用那个Button的!

个人建议最好还是使用代码调用,代码已经在上面贴出来可~

 

2017年11月26号补充:

其实Unity给我们提供了CataLog,可以很方便的添加各个平台不同的id,而只使用同样的加载方式即可。

下面给出获取Catalog数据方式的代码:(方便以后其他平台移植,到时候直接在catalog里面设置就好啦)

 

public void InitializePurchasing() 
	{
		if (IsInitialized())
		{
			return;
		}
		StandardPurchasingModule module = StandardPurchasingModule.Instance();
		module.useFakeStoreUIMode = FakeStoreUIMode.StandardUser;
		ConfigurationBuilder builder = ConfigurationBuilder.Instance(module);
		//通过编辑器中的Catalog添加,方便操作
		ProductCatalog catalog = ProductCatalog.LoadDefaultCatalog();
		foreach (var product in catalog.allProducts) {
			if (product.allStoreIDs.Count > 0) {
				var ids = new IDs();
				foreach (var storeID in product.allStoreIDs) {
					ids.Add(storeID.id, storeID.store);
				}
				builder.AddProduct(product.id, product.type, ids);
			} else {
				builder.AddProduct(product.id, product.type);
			}
		}
		UnityPurchasing.Initialize(this, builder);
	}


这样就可以不用管这边加载代码,其他地方都是通用的。哈哈哈

 

 

 

2017.12.19日补充,发现好多新手小伙伴还是一脸懵逼,我今天把最新的方式发在这里,供大家参考:

本次使用的还是和catalog结合使用,另外关于商品id还不清楚的我在这里再说一下。我这里有两个商品ID,第一个是Unity这边使用的ID,用于Unity端调用,也是用来映射不同平台商品id的自定义ID,他填的位置如下:

这个id可以自定义,也可以用实际商店的id,只要是一个字符串就可以了。

然后第二个ID,就是个个平台设置的商品ID了,他们可能会有各种格式,所以unity这边做了处理,第一个自定义的id就是多了一层映射,方便管理和使用,了解了这一层关系,就知道我为什么要推荐用catalog了。因为在移植到其他平台时,我只要维护catalog的的每个平台的id就可以了,代码那边都需要关心。

 

明白这两个ID的作用,那么就可以直接看我的下面的代码了,我把ShopList.cs都贴出来,相信大家能明白其中的原由了。

 

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

/// <summary>
/// 这是通用方式,通过读取catalog里面的信息,获取所有商品信息
/// </summary>
public class ShopList : MonoBehaviour, IStoreListener{
	private static IStoreController m_StoreController;
	private static IExtensionProvider m_StoreExtensionProvider;
	bool blnRestore = false;//用来表示
	bool blnPressRestore = false;//用来区分是否按了	restore 按钮

	int dataLen = 3;
	//每个商品的内容(金币),价格(除以100),折扣
	int[] shopData = new int[]{
		150,99,0,	//0.99 美元购买 150 个金币
		450,299,0,	//2.99 美元购买 450 个金币
		850,499,12,	//4.99 美元购买 850 个金币
		1850,999,22,	//9.99 美元购买 1850 个金币
		3950,1999,30,	//19.99 美元购买 3950 个金币
		0,199,0		//1.99 美元购买去广告功能,可以Restore的项目
	};

	//catalog 里面设置的id,和这边一一对应,这个id是unity端的一个映射,在catalog里面可以对应不同平台的真实的 商品id
	private string[] kProducts = new string[] {
		"buycoins0",
		"buycoins1",
		"buycoins2",
		"buycoins3",
		"buycoins4",
		"removeads"
	};
	void Start () {
		InitializePurchasing();
	}
	private bool IsInitialized()
	{
		return m_StoreController != null && m_StoreExtensionProvider != null;
	}
	//初始化内购项目,主要是从catalog中获取商品信息,设置给 UnityPurchasing
	void InitializePurchasing() 
	{
		if (IsInitialized())
		{
			Debug.Log("初始化失败");
			return;
		}
		StandardPurchasingModule module = StandardPurchasingModule.Instance();
		module.useFakeStoreUIMode = FakeStoreUIMode.StandardUser;
		ConfigurationBuilder builder = ConfigurationBuilder.Instance(module);
		//通过编辑器中的Catalog添加,方便操作
		ProductCatalog catalog = ProductCatalog.LoadDefaultCatalog();
		// Debug.Log(catalog.allProducts.Count);
		foreach (var product in catalog.allProducts) {
			if (product.allStoreIDs.Count > 0) {
				// Debug.Log("product:" + product.id);
				var ids = new IDs();
				foreach (var storeID in product.allStoreIDs) {
					ids.Add(storeID.id, storeID.store);
					// Debug.Log("stordId:" + storeID.id  + ", " + storeID.store);
				}
				builder.AddProduct(product.id, product.type, ids);
			} else {
				builder.AddProduct(product.id, product.type);
			}
		}
		UnityPurchasing.Initialize(this, builder);
	}

	//供外部调用,当按 Restore 按钮时触发
	public void OnRestore(){
		if (Application.platform == RuntimePlatform.OSXEditor || Application.platform == RuntimePlatform.WindowsEditor){
			Debug.Log("Restore success!");
		}else{
			blnRestore = true;
			RestorePurchases();
			blnPressRestore = true;
		}
	}
	//供外部调用,按下哪个按钮,就可以购买哪一档的金币,我这里是通过按钮的名称得到购买的 idx 的,可以根据自己需要更改,比如:OnBuyCoins(int idx)
	//idx 是上面 shopData 对应的每行数据
	public void OnBuyCoins(Button btn){
		int idx = System.Convert.ToInt32(btn.name);
		BuyCoinsWithIdx(idx);
	}
	//实际购买调用的函数,根据idx拿到unity端的商品id
	void BuyCoinsWithIdx(int idx){
		if (idx == 5){//购买去广告
			if (Application.platform == RuntimePlatform.OSXEditor || Application.platform == RuntimePlatform.WindowsEditor){
				Debug.Log("购买去广告!");
			}else{
				blnPressRestore = false;
				BuyProductID(kProducts[idx]);
			}
		}else{
			if (Application.platform == RuntimePlatform.OSXEditor || Application.platform == RuntimePlatform.WindowsEditor){
				Debug.Log("editor buy coins");
			}else{
				BuyProductID(kProducts[idx]);
			}
		}
	}
	//这里是通过商品id购买物品
	void BuyProductID(string productId)
	{
		if (IsInitialized())
		{
			Debug.Log("Buy ProductID: " + productId);
			Product product = m_StoreController.products.WithID(productId);
			if (product != null && product.availableToPurchase) {
				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("没出初始化");
		}
	}
	//真是的发起Restore请求
	public void RestorePurchases()
	{
		if (!IsInitialized()) {
			Debug.Log("没出初始化");
			return;
		}
		if (Application.platform == RuntimePlatform.IPhonePlayer || Application.platform == RuntimePlatform.OSXPlayer) {
			// Debug.Log("RestorePurchases started ...");
			var apple = m_StoreExtensionProvider.GetExtension<IAppleExtensions>();
			apple.RestoreTransactions(HandleRestored);
		}else {
			Debug.Log("RestorePurchases FAIL. Not supported on this platform. Current = " + Application.platform);
		}
	}
	//如果restore之后,会返回一个状态,如果状态为true,那边以前购买的非消耗物品都会回调一次 ProcessPurchase 然后在这里个回调里面进行处理
	void HandleRestored(bool result){
		//返回一个bool值,如果成功,则会多次调用支付回调,然后根据支付回调中的参数得到商品id,最后做处理(ProcessPurchase)
		// Debug.Log("RestorePurchases continuing: " + result + ". If no further messages, no purchases available to restore.");
		blnRestore = false;
		if (result){
			Debug.Log("Restore success!");
		}else{
			Debug.Log("Restore Failed!");
		}
	}
	//初始化回调
	public void OnInitialized(IStoreController controller, IExtensionProvider extensions){
		//初始化成功
		Debug.Log("OnInitialized: PASS");
		m_StoreController = controller;
		m_StoreExtensionProvider = extensions;
	}
	public void OnInitializeFailed(InitializationFailureReason error){
		//初始化失败
		Debug.Log("OnInitializeFailed InitializationFailureReason:" + error);
		if (error == InitializationFailureReason.AppNotKnown){
			//
		}else if (error == InitializationFailureReason.NoProductsAvailable){
			//
		}else if (error == InitializationFailureReason.PurchasingUnavailable){
			//
		}
	}
	//购买成功后的回调,包括restore的商品
	public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args) 
	{
		// Debug.Log(string.Format("ProcessPurchase: PASS. Product: '{0}'", args.purchasedProduct.definition.id));
		//根据不同的id,做对应的处理。。
		int key = -1;
		for(int i=0;i<kProducts.Length;i++){
			if (string.Equals(args.purchasedProduct.definition.id, kProducts[i], System.StringComparison.Ordinal)){
				key = i;
				break;
			}		
		}
		if (key == -1){
			Debug.Log(string.Format("ProcessPurchase: FAIL. Unrecognized product: '{0}'", args.purchasedProduct.definition.id));
		}else{
			if (key == 5){
				if (!blnPressRestore){
					Debug.Log("Ads have been removed!");
				}
			}else{
				Debug.Log("购买了"+shopData[key*dataLen].ToString()+"个金币");
			}
		}
		return PurchaseProcessingResult.Complete;
	}
	//购买失败回调,根据具体情况给出具体的提示
	public void OnPurchaseFailed(Product product, PurchaseFailureReason failureReason)
	{
		//支付失败
		Debug.Log(string.Format("OnPurchaseFailed: FAIL. Product: '{0}', PurchaseFailureReason: {1}", product.definition.storeSpecificId, failureReason));
		if (failureReason == PurchaseFailureReason.UserCancelled){
			//用户取消交易
		}else if (failureReason == PurchaseFailureReason.ExistingPurchasePending){
			//上一笔交易还未完成
		}else if (failureReason == PurchaseFailureReason.PaymentDeclined){
			//拒绝付款
		}else if (failureReason == PurchaseFailureReason.ProductUnavailable){
			//商品不可用
		}else if (failureReason == PurchaseFailureReason.PurchasingUnavailable){
			//支付不可用
		}else{
			//位置错误
		}
	}
}

 

 

 

 

 

具体使用,就是把上面这个脚本挂载到一个场景物体上,然后在Button里面设置按钮事件,我现在的写法是根据按钮的名字来得到需要购买的商品序号,通过序号拿到商品的Unity端ID,最后就可以发起购买啦。

 

当然,这个方式也可以根据自己的需求去改,比如传递一个固定序号等等,不需要用按钮名称。

 

2019.3.18补充:

使用了最新版本的UnityIAP插件,在使用Catalog初始化IAP时,可以用下面代码了,可以更加的方便,并且少了出错的几率,特别是自动恢复:

void InitializePurchasing () {
		if (IsInitialized ()) {
			return;
		}
		StandardPurchasingModule module = StandardPurchasingModule.Instance ();
		module.useFakeStoreUIMode = FakeStoreUIMode.StandardUser;
		ConfigurationBuilder builder = ConfigurationBuilder.Instance (module);
		//通过编辑器中的Catalog添加,方便操作
		ProductCatalog catalog = ProductCatalog.LoadDefaultCatalog ();
		//新的方法,直接调用Unity给的解析函数去加载Catalog
		IAPConfigurationHelper.PopulateConfigurationBuilder (ref builder, catalog);
		UnityPurchasing.Initialize (this, builder);
	}

主要的新方法就是 IAPConfigurationHelper.PopulateConfigurationBuilder 了,是不是很方便啦~

 

  • 9
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 40
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 40
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

苏小败在路上

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值