参考: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();
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;
///
/// 这是通用方式,通过读取catalog里面的信息,获取所有商品信息
///
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();
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
具体使用,就是把上面这个脚本挂载到一个场景物体上,然后在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 了,是不是很方便啦~