简述:最近项目要接入订阅商品,这里总结一下公司大佬们的经验和我整理后脚本。
一、关于订阅
1,跟消耗性和非消耗性的购买类似,开发者账后后台建订阅型商品。 订阅型商品分两种,自动续订和非自动续订的,一般用的是自动续订的。
2,有免费期,如1个月的订阅期,有3天的免费试用期,用户在购买后,前三天如果退订,是不收费的。 如果过了试用期后,用户可以再苹果的设置里面把后面的续订取消掉,这样,一个订阅周期结束后,苹果就不会再自动扣费了。
3、针对订阅商品我们需要解决的问题
a、用户卸载后重新安装,如何恢复订阅的功能
可以用苹果自带的一套恢复机制,如果一个用户订阅了这个app的一个商品,用户再次购买时,苹果会有自己的一套恢复机制。它会提示你之前已经订阅过改商品,为你恢复。所以恢复的机制我们可以直接采用苹果自带的方案
b、如何验证用户的订阅是否结束!
*用户每次购买的商品,苹果都会生成一个receipt,这个相当于我们购买时的一个凭证,这个凭证是可以用来验证这次的购买是否生效的。购买的凭证是苹果自己保存的,所以我们要验证这个产品是否购买过,只需要验证这个receipt就可以了。
验证的方式有两种:
一种是本地的验证,根据苹果的机制,解析到receipt后,会得到相应的字段。但是这种检测的机制已经被破解了,因为hacker可以修改本地的receipt,造成本地的验证失效。但是对于内购不是占据特别大的比例的产品,这个是可以使用的。像我们的休闲类小游戏,内购比重不大可以采用这种方式。
另外一种方式,是通过把receipt发给苹果,苹果通过服务器自己验证。这种验证是100%准确的,后台可以通过苹果解析后的字段,判断本次购买是否准确的。
对于订阅性的商品,如果要通过服务端验证,需要后台添加一个配置,这个字段在服务端针对订阅性商品做验证的时候需要用,这是跟消耗性和非消耗性不同的地方。
而谷歌判断有没有过期只需要autoRenewing属性就可以了 ,如果为true就处于订阅状态,false就是订阅失效了。
c、我们如何测试订阅
订阅的沙盒环境和其它类型的有些不一样,它的过期时间和后台设置的不一致,具体参照下表。
那么我们客户端要做的操作是什么呢?
一、购买
订阅商品的购买与一般商品购买没有区别,按照正常流程购买就好了。
这点有问题的可以参照我的另外一篇博客Unity IAP 谷歌支付,ios支付
注意一点,初始化商品类型的时候订阅产品要选对类型。
如图位置所指位置,订阅商品要选订阅类型(ProductType.Subscription)。
二、验证
订阅商品的难点主要在于判断订阅商品是否到期
。
由于Unity自带的IAP插件没有找到解析receipt的接口,我们需要导入以下脚本用来解析receipt
using UnityEngine;
class GooglePurchaseData
{
// INAPP_PURCHASE_DATA
public string inAppPurchaseData;
// INAPP_DATA_SIGNATURE
public string inAppDataSignature;
public GooglePurchaseJson json;
[System.Serializable]
private struct GooglePurchaseReceipt
{
public string Payload;
}
[System.Serializable]
private struct GooglePurchasePayload
{
public string json;
public string signature;
}
[System.Serializable]
public struct GooglePurchaseJson
{
public string autoRenewing;
public string orderId;
public string packageName;
public string productId;
public string purchaseTime;
public string purchaseState;
public string developerPayload;
public string purchaseToken;
}
public GooglePurchaseData(string receipt)
{
try
{
var purchaseReceipt = JsonUtility.FromJson<GooglePurchaseReceipt>(receipt);
var purchasePayload = JsonUtility.FromJson<GooglePurchasePayload>(purchaseReceipt.Payload);
var inAppJsonData = JsonUtility.FromJson<GooglePurchaseJson>(purchasePayload.json);
inAppPurchaseData = purchasePayload.json;
inAppDataSignature = purchasePayload.signature;
json = inAppJsonData;
}
catch
{
Debug.Log("Could not parse receipt: " + receipt);
inAppPurchaseData = "";
inAppDataSignature = "";
}
}
}
脚本2 判断是否处于订阅状态
using UnityEngine.Purchasing;
using UnityEngine.Purchasing.Security;
/*
以下两个方法都是封装好检测是否订阅过的方法。
仅针对有一个订阅商品的检测,多个订阅商品可能需要区分ID。暂时未做测试。
注意一点,判断订阅状态必须得在商品初始化之后判断。
*/
//订阅控制脚本
public class SubscriptionCtrl:MonoBehaviour
{
public void Start()
{
//每次进入游戏调用检查订阅方法
#if UNITY_ANDROID
CheckSubscribeReceiptAndorid();
#elif UNITY_IOS || UNITY_STANDALONE_OSX
CheckSubscribeReceipt();
#endif
}
public void CheckOk()
{
//如果检查处于订阅状态,可以在这里做客户端逻辑处理
}
public void CheckWrong()
{
//如果检查处于非订阅状态,可以在这里做客户端逻辑处理
}
//检查谷歌订阅状态的方法,该方法需要同时导入另外一个脚本GooglePurchaseData 解析谷歌支付的receipt
public void CheckSubscribeReceiptAndorid()
{
foreach (Product p in Purchaser.m_StoreController.products.all)
{
if (p.hasReceipt)
{
Debug.Log("recepit all:" + p.receipt);
GooglePurchaseData data = new GooglePurchaseData(p.receipt);
Debug.Log("recepit autoRenewing:" + data.json.autoRenewing);
/*
Debug.Log("recepit orderId:" + data.json.orderId);
Debug.Log("recepit packageName:" + data.json.packageName);
Debug.Log("recepit productId:" + data.json.productId);
Debug.Log("recepit purchaseTime:" + data.json.purchaseTime);
Debug.Log("recepit purchaseState:" + data.json.purchaseState);
Debug.Log("recepit purchaseToken:" + data.json.purchaseToken);
*/
if (bool.Parse(data.json.autoRenewing))
{
CheckOk();
Debug.Log("sub is active");
}
else
{
CheckWrong();
Debug.Log("sub not active");
}
}
}
}
//检查IOS订阅状态的方法, 这里是通过receipt里面的expiredate和当前时间对比来判断当前的订阅商品是否过期。
public void CheckSubscribeReceipt()
{
try
{
var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());
// Get a reference to IAppleConfiguration during IAP initialization.
var appleConfig = builder.Configure<IAppleConfiguration>();
if (appleConfig == null || string.IsNullOrEmpty(appleConfig.appReceipt))
{
return;
}
//Debug.LogError("appReceipt:" + appleConfig.appReceipt);
var receiptData = System.Convert.FromBase64String(appleConfig.appReceipt);
if (receiptData == null)
{
return;
}
AppleReceipt receipt = new AppleReceiptParser().Parse(receiptData);
if (receipt == null)
{
return;
}
foreach (AppleInAppPurchaseReceipt productReceipt in receipt.inAppPurchaseReceipts)
{
if (productReceipt.productType == 3)
{
Debug.Log("sub productid = " + productReceipt.productID);
DateTime expirationDate = productReceipt.subscriptionExpirationDate;
Debug.Log("sub ExpirationDate = " + expirationDate.ToString());
DateTime now = DateTime.Now.ToUniversalTime();
//DateTime cancellationDate = apple.cancellationDate;
if (DateTime.Compare(now, expirationDate) < 0)
{
Debug.Log("sub is active");
CheckOk();
}
else
{
CheckWrong();
Debug.Log("sub not active");
}
}
}
}
catch (Exception exp)
{
Debug.Log(exp);
}
}
}
以上 是客户端需要对订阅商品的操作。
另外,订阅页面在UI显示的方面也要遵循一些规则,具体可以参考官方文档。晚些也会更新一篇关于后台配置和遵循规则的博客。