Unity接入OneStore内购

Posted PassionY

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unity接入OneStore内购相关的知识,希望对你有一定的参考价值。

前言

OneStore是韩国第一大android应用市场,访问官网可能需要你科学上网才能正常访问。
[中文]OneStore开发工具
[中文]在Unity中使用ONE store In-App支付

国内手机可能不支持onestore,需要下载onestore的server和onestore的商店app

app:https://m.onestore.co.kr/mobilepoc/etc/marketDownloadGuide.omp
server:https://m.onestore.co.kr/mobilepoc/etc/downloadGuide.omp

设置OneStore

Github onestore_iap_release

下载地址

插件导入目录

Unity Plugin配置

Assets > Plugins > Android

文件详情
AndroidManifest.xmlProject Settings > Player > Publishing Settings > Build
Iap_adapter-v1.3.0.aar负责Unity与ONE store In-App支付library的通信。
Iap_sdk-v19.00.00.aarONE store In-App支付library文件

Assets > Scripts > Purchase

文件详情
AndroidNative.cs在Unity中启动安卓 AlertDialog的实用程序文件。
GaaIapCallbackManager.cs负责从Unity调用Android发出的响应结果。
GaaIapCallManager.cs负责在Unity中调用In-App支付SDK中的函数。
GaaIapResultListener.cs接收来自GaaIapCallbackManager的响应结果,并加工数据以方便UI的写入。
GaaPurchaseResponse.csIn-App支付library中使用的Value Object定义的文件。

Assets > StreamingAssets

文件详情
global-appstores.json在In-App支付SDK中查找Payment module所需的文件。

初始化OneStore IAP

初始化并连接ONE store In-App支付
请求In-App支付之前,您必须通过以下操作连接到ONE store服务。

当您尝试通过GaaIapCallManager.StartConnection()连接时,将在内部开始初始化SDK,并尝试与支付模块连接。此时,请参照global-appstores.json中定义的支付模块信息。

using Gaa
public class YourUiScript: MonoBehaviour

    ...
    public void StartConnection()
    
        if (GaaIapCallManager.IsServiceAvailable() == false)
        
            GaaIapCallManager.StartConnection( /* your public key */ );
        
    
    ...

可以通过GaaIapResultListener.PurchaseClientStateEvent()获取有关确认连接响应的方法。

响应结果以GaaPurchaseResponse.IapResult对象的形式传递。

IapResult.code的值可通过GaaPurchaseResponse.ResponseCode查看。

void PurchaseClientStateEvent(IapResult iapResult)

    if (iapResult.IsSuccess())
    
        ...
    
    else if (iapResult.code == ResponseCode.RESULT_NEED_UPDATE)
    
        ...
    
    else if (iapResult.code == ResponseCode.RESULT_NEED_LOGIN)
    
        ...
    

退出应用软件时,您必须断开与支付模块的连接。

void OnDestroy(

    GaaIapCallManager.Destroy();

查询App商品信息

在开发者中心注册In-App商品时,生成的In-App商品ID用于查询In-App商品详情。如需查看In-App商品详情,请调用QueryProductDetails()。指定In-App商品ID(productId)数组的值及其对应的商品类型。

In-App商品类型包括管理型商品、包月型商品,如果想要同时查看这两种类型,只需输入ProductType.ALL即可。

为了查询App商品详细信息,应用软件使用在ONE store开发者中心配置商品时定义的In-App商品ID来查询。 有关更多信息,请参照开发人员中心。

可通过GaaIapResultListenerProductDetailsSuccessEvent()ProductDetailsErrorEvent()接收商品信息查询的响应结果值。

与Android和Unity的通信是字符串通信,由于一次传递的字符串长度有限,每次传递一个商品。

因此,ProductDetailsSuccessEvent(...,...,int count,int totalCount)值很重要。

Ex)1件商品:count=1,totalCount=1/10件商品:count=1,totalCount=10…。
Count=10,totalCount=10。

string[] all_products =  inapp_p5000, inapp_p10000, inapp_p50000, auto_a100000 ;
GaaIapCallManager.QueryProductDetails(all_products, ProductType.ALL);
List<ProductDetail> products = new List<ProductDetail>();
void ProductDetailsSuccessEvent(ProductDetail productDetail, int count, int totalCount)

    if (count == 1)
        products.Clear();
    
    products.Add(productDetail);
    
    if (count == totalCount)
        // send ProductDetail List to UI
    

void ProductDetailsErrorEvent(IapResult iapResult)

    if (iapResult.code == ResponseCode.RESULT_NEED_UPDATE)
    
        ...
    
    else if (iapResult.code == ResponseCode.RESULT_NEED_LOGIN)
    
        ...
    

In-App商品ID目录必须由应用软件自己的安全后端服务器管理。

请求购买

要购买In-App商品,请调用GaaIapCallManager.LaunchPurchaseFlow()

在调用时创建并指定GaaPurchaseResponse.PurchaseFlowParams对象。必需的值包括In-App商品ID(productId)和商品类型(管理型商品:ProductType.INAPP,包月型商品:ProductType.AUTO)。

此外,还放入开发商随机输入的developerPayload(最多200byte)。此值可用于在付款后检查数据的一致性和附加数据。

ProductName用于更改在支付时向用户展示的In-App商品的名称,而不是注册的In-App商品ID的名称。

ONE store为用户提供了折扣券、返现等优惠促销。

开发商可以使用gameUserIdpromotionApplication值在购买请求时允许或限制使用app的用户参与促销。

开发商通过选择该应用软件的唯一用户识别号以及是否参与促销活动进行传递,而ONE store将根据这些值应用用户的促销优惠。

    void BuyProduct(string productId, string type)
    
        PurchaseFlowParams param = new PurchaseFlowParams();
        param.productId = productId;
        param.productType = type;
        //param.productName = "";
        //param.devPayload = "your Developer Payload";
        //param.gameUserId = "";
        //param.promotionApplicable = false;

        GaaIapCallManager.LaunchPurchaseFlow(param);
    

调用LaunchPurchaseFlow()Method时,将显示ONE store的支付界面。
下图表示ONE store的支付界面。

支付的响应通过GaaIapResultListener的事件传递。
成功:PurchaseUpdatedSuccessEvent()
失败:PurchaseUpdatedErrorEvent()

由于“查询商品信息”等原因,count、totalCount都存在。

private List<PurchaseData> purchases = new List<PurchaseData>();
private List<string> signatures = new List<string>();

void PurchaseUpdatedSuccessEvent(PurchaseData purchaseData, string signature, int count, int totalCount)

    if (purchaseData != null) 
        if (count == 1)
            purchases.Clear();
            signatures.Clear();
        
        purchases.Add(purchaseData);
        signatures.Add(signature);
  
        if (count == totalCount)
            OnPurchaseUpdatedResponse(purchases, signatures);
        
    else
        // no PurchaseData
    

  
void PurchaseUpdatedErrorEvent(IapResult iapResult)

    ...

支付成功后,将创建一个唯一标识符“购买token”,即表示用户购买的购买ID。在应用软件中,您可以将购买token存储在本地,或者存储在安全后端服务器上,用于确认购买。

管理型商品的购买token对于每个购买ID都是唯一的,但包月型商品的购买token在更新期间保持不变。

用户还将通过电子邮件收到包含收据号码的交易收据。每次购买管理型商品时,您都会收到电子邮件,包月型产品在第一次购买时以及以后更新时都会收到电子邮件。

确认购买

如果您使用的是ONE store IAP library v6或更高版本,则必须在3天内进行购买确认。如果无法确认购买,购买金额将退还。

您可以使用以下method之一确认购买:

对于消耗性产品,请使用 GaaIapCallManager.Consume()。
非消耗性产品,请使用 GaaIapCallManager.Acknowledge()
对于包月型商品,只需确认首次支付的购买即可。

PurchaseData对象包含isAcknowledged()method,该method显示是否已确认购买。在确认购买之前,使用这些method,您可以确定您的购买是否已被确认。

下面是显示包月型商品的购买确认方法的示例。

void PurchaseUpdatedSuccessEvent(PurchaseData purchaseData, string signature, int count, int totalCount)

    ...
    if (purchaseData != null) 
        ...
        if (purchaseData.IsAcknowledged() == false)
        
            GaaIapCallManager.Acknowledge(purchaseData, "your developer payload");
        
    
    ...

请求登录ONE store
ONE store In-App支付SDK必须登录后才能运行。

如果您以前登录过,ONE store会自动登录,但如果您需要登录,IapResult.code会在API请求中发送到ResponseCode.RESULT_NEED_LOGIN中。

传递RESULT_NEED_LOGIN后,必须调用GaaIapCallManager.LaunchLoginFlow()method。

响应的结果可通过GaaIapResultListener.LoginFlowEvent()查看。

下面是请求登录ONE store的示例。

GaaIapCallManager.LaunchLoginFlow();
void LoginFlowEvent(IapResult iapResult)

    if (iapResult.IsSuccess()) 
        // You need to specify the scenario after successful login.
    

安装ONE store服务
如果ONE store服务的版本较低或不存在,则不能使用In-App支付。

当尝试通过GaaIapCallManager.StartConnection()连接时,如果响应结果导致ResponseCode.RESULT_NEED_UPDATE,则需要调用GaaIapCallManager.LaunchUpdateOrInstallFlow()method。

响应的结果可通过GaaIapResultListener.UpdateOfInstallFlowEvent()查看。

以下是如何安装ONE store服务的示例。

GaaiapCallManager.LaunchUpdateOrInstallFlow();
void UpdateOrInstallFlowEvent(IapResult iapResult)

    if (iapResult.IsSuccess()) 
        // If the installation is completed successfully,
        // you should try to reconnect with the ONE store service.
        GaaIapCallManager.StartConnection("your public key")
     else 
        ...
    

最后

Unity技术开发干活 - 总目录

Unity精选 - 专栏目录
Unity之ASE入门到精通 - 专栏目录
UniRx入门到精通 - 专栏目录

Unity 之 接入IOS内购过程解析文末源码

前言

看完此文章你可以了解IOS内购接入全过程,可以学习到Unity从零接入内购功能。另外此博文和文末源码没有涉及到掉单补单部分逻辑。

需要准备

  • 一台mac系统机器
  • 苹果开发者账号
  • Unity2019.4.x (不同版本,3步骤略有不同)
  • Xcode (我的版本12.5)

内购导图

一,效果展示


二,苹果后台

PS:若公司已有运营人员在后台操作过了,可以跳过此步骤。注意测试机上还是需要登陆沙箱账号奥。

2.1 注册应用程序

  1. 首先打开苹果开发者官网:https://developer.apple.com/

  2. 点击登陆并点“Account”,在Apple Developer Center中,导航到相应的标识符部分:

  1. 添加新的 App ID 以创建与 Apple 的基本应用程序实体。

    • 注意:使用显式应用程序 ID。通配符应用 ID (com.example.*) 不能用于使用应用内购买的应用。

    • 注意:在开发者中心创建 App ID 后,即可在 iTunes Connect 中使用它。

  1. 导航到iTunes Connect并创建一个应用程序,以与游戏建立商店关系:

  1. 使用新创建的 App ID 作为应用的 Bundle ID:

2.2 添加应用内购买

  1. 选择功能并使用加号 ("+") 按钮添加新的应用内购买:

  2. 选择产品类型:

  1. 指定产品标识符,并根据要求填写其他字段。
  • 注意:此处的“产品 ID”与游戏源代码中使用的标识符相同,通过AddProduct()或AddProducts()添加到Unity IAP ConfigurationBuilder实例。

2.3 测试IAP

  1. 使用iTunes Connect创建沙盒测试器以在您的测试设备的iTunes帐户上使用。为此,请导航至iTunes Connect > Users and Roles,然后选择加号 ("+") 按钮。

PS:详情可查看AppleSandbox Tester文档,。

  1. Xcode项目配置
    Xcode 项目中Bundle IdentitifierTeamiTunes Connect 中使用的一致
    PS:Unity中的包名也应该保持一致

  1. 在测试设备登陆沙箱测试账号

三,下载IAP包

3.1 下载Package

打开Windows -> Package Manager 下载 In App Purchasing

3.2 打开Srever配置

  1. 打开服务窗口,在服务窗口中查找和启用应用内购买

  2. 选择项目ID(当前登录的账号)

  3. 启用In-APP Purchasing (有的时候切换慢,需要等一会)

  4. 回答问题
    问:这款应用主要面向13岁以下的儿童(是就勾选,不是不勾选)

  5. 有个报错
    我没有解决也没有影响,需要解决的话按照下面的提示操作一下


四,代码逻辑

4.1 逻辑分析

  1. 实现IStoreListener接口,接口提供四个回调函数,分别是初始化成功、失败,购买成功、失败;

  2. 编写初始化逻辑,完善初始化成功、失败回调接口函数;

  3. 编写调用购买逻辑,完善购买成功、失败回调接口函数;

  4. 实际开发中需要限制,购买按钮只被点击一次。

代码结构就是这样了,详细解释代码注释已经写得很清楚了,这里不再赘述。

使用时将代码挂载到场景即可进行初始化,然后创建Button监听代码中的OnClickPurchase方法即可打包测试。

PS:注意需要将goodsList数组中的key换成你后台申请的

4.2 示例源码

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

/// <summary>
/// IAP管理类
/// </summary>
public class IAPManager : MonoBehaviour, IStoreListener
{
    /// <summary>
    /// 需要换成对应游戏后台的key
    /// </summary>
    private string[] goodsList = new string[]
    {
        "com.czhenya.gold.1",
        "com.czhenya.gold.2",
        "com.czhenya.gold.3"
    };

    // 控制器
    private IStoreController controller;

    // 苹果扩展
    private IAppleExtensions appleExtensions;

    // 谷歌商店扩展
    private IGooglePlayStoreExtensions googlePlayStoreExtensions;

    // 是否可以发起购买
    private bool isCanOnClickBubBtn = false;

    void Start()
    {
        Init();
    }

    /// <summary>
    /// 初始化
    /// </summary>
    private void Init()
    {
        // 没有网络,IAP会一直初始化
        if (Application.internetReachability == NetworkReachability.NotReachable)
        {
            Debug.Log("----- 用户没有连接网络 IAP不可用 ------");
        }

        var module = StandardPurchasingModule.Instance();
        ConfigurationBuilder builder = ConfigurationBuilder.Instance(module);
        // builder.AddProduct("商品id1", ProductType.Consumable); 
        // ProductType :和后台说明对应
        // consumable:可消费的,如游戏中的金币,用完还可以再购买。
        // non-consumable:不可销毁的,一次购买,永久生效。比如去广告,解锁游戏关卡,这种商品只能购买一次。
        // subscription:订阅的,这种一般用于新闻、杂志、或者app里面的月卡。可以按月或者按年收费。
        for (int i = 0; i < goodsList.Length; i++)
        {
            builder.AddProduct(goodsList[i], ProductType.Consumable);
        }

        // 开始初始化
        UnityPurchasing.Initialize(this, builder);
    }

    /// <summary>
    /// 初始化成功 -- 接口函数
    /// </summary>
    /// <param name="controller"></param>
    /// <param name="extensions"></param>
    public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
    {
        Debug.Log("【Unity IAP】初始化成功 IAP initialize success");
        isCanOnClickBubBtn = true;
        this.controller = controller;

        // 回调赋值
        this.appleExtensions = extensions.GetExtension<IAppleExtensions>();
        this.googlePlayStoreExtensions = extensions.GetExtension<IGooglePlayStoreExtensions>();

        //登记 购买延迟 监听器
        appleExtensions.RegisterPurchaseDeferredListener(OnDeferred);
    }

    //购买延迟提示
    private void OnDeferred(Product item)
    {
        Debug.Log("【Unity IAP】 网速慢.................");
    }

    /// <summary>
    /// 初始化失败回调 -- 接口函数
    /// </summary>
    /// <param name="error"></param>
    public void OnInitializeFailed(InitializationFailureReason error)
    {
        Debug.LogError("【Unity IAP】初始化失败 OnInitializeFailed, reason:" + error.ToString());
    }

    /// <summary>
    /// 购买失败回调 -- 接口函数
    /// </summary>
    /// <param name="i"></param>
    /// <param name="p"></param>
    public void OnPurchaseFailed(Product i, PurchaseFailureReason p)
    {
        Debug.LogError("【Unity IAP】购买失败 OnPurchaseFailed,reason:" + p.ToString());
        if (this.onPurchaseFailed != null)
        {
            this.onPurchaseFailed();
            this.onPurchaseFailed = null;
        }
    }

    /// <summary>
    /// 购买成功回调 -- 接口函数
    /// </summary>
    /// <param name="e"></param>
    /// <returns></returns>
    public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs e)
    {
        Debug.LogError("【Unity IAP】购买过程 purchase finished, apple return receipt:" + e.purchasedProduct.receipt);

        if (this.onPurchaseSuccess != null)
        {
            this.onPurchaseSuccess(e.purchasedProduct.receipt);
            this.onPurchaseSuccess = null;
        }

        return PurchaseProcessingResult.Complete;
    }

    /// <summary>
    /// 支付失败回调
    /// </summary>
    private Action onPurchaseFailed;

    /// <summary>
    /// 支付成功回调
    /// </summary>
    private Action<string> onPurchaseSuccess;

    /// <summary>
    /// 购买产品
    /// </summary>
    /// <param name="productId">产品ID</param>
    /// <param name="onFailed">失败回调</param>
    /// <param name="onSuccess">成功回调</param>
    public void PurchaseProduct(string productId, Action onFailed, Action<string> onSuccess)
    {
        this.onPurchaseFailed = onFailed;
        this.onPurchaseSuccess = onSuccess;

        if (controller != null)
        {
            var product = controller.products.WithID(productId);
            if (product != null && product.availableToPurchase)
            {
                Debug.Log("【Unity IAP】开始购买");
                controller.InitiatePurchase(productId);
            }
            else
            {
                Debug.LogError("【Unity IAP】失败回调 no product with productId:" + productId);
                if (this.onPurchaseFailed != null)
                {
                    this.onPurchaseFailed();
                }
            }
        }
        else
        {
            Debug.LogError("【Unity IAP】失败回调 controller is null,can not do purchase");
            if (this.onPurchaseFailed != null)
            {
                this.onPurchaseFailed();
            }
        }
    }

    /// <summary>
    /// 发起购买函数  -- 商城按钮监听
    /// </summary>
    /// <param name="i"></param>
    public void OnClickPurchase(int i)
    {
        // 正式项目时需限制 -- 不允许多次点击 

        Debug.Log("【Unity IAP】发起购买函数 " + Application.internetReachability);
        if (Application.internetReachability == NetworkReachability.NotReachable)
        {
            Debug.Log("【Unity IAP】用户没网... ");
            return;
        }

        PurchaseProduct(goodsList[0], OnBuyFailed, OnBuySuccess);
    }

    /// <summary>
    /// 购买失败回调
    /// </summary>
    void OnBuyFailed()
    {
        Debug.Log("【Unity IAP】购买失败回调 OnBuyFailed...");
    }

    /// <summary>
    /// 购买成功回调
    /// </summary>
    /// <param name="str"></param>
    void OnBuySuccess(string str)
    {
        Debug.Log("【Unity IAP】购买成功回调 OnBuySuccess..." + str);
        //会得到下面这样一个字符串
        //{"Store":"AppleAppStore",
        //"TransactionID":"1000000845663422",
        //"Payload":"MIIT8QYJKoZIhvcNAQcCoIIT4jCCE94CAQExBBMMIIBa ... 还有N多 ..."}
    }
}

五,打包测试

代码配置和手动配置选择一个习惯用的方式即可。

5.1 代码配置

由于内购需要系统库StoreKit.frameworkiAd.framework。为了不每次打包Xcode时都手动添加,所以创建打包配置代码。(复制下面文件,放到Editor文件夹下)

using System.IO;
using UnityEditor;
using UnityEngine;
#if UNITY_IOS
using UnityEditor.Callbacks;
using UnityEditor.iOS.Xcode;
#endif

/// <summary>
/// 打包自动配置文件
/// </summary>
public class CZYConfigEditor
{
#if UNITY_IOS
    [PostProcessBuildAttribute(100)]
    public static void onPostProcessBuild(BuildTarget target, string targetPath)
    {
        if (target != BuildTarget.iOS)
        {
            return;
        }

        string projPath = PBXProject.GetPBXProjectPath(targetPath);
        PBXProject proj = new PBXProject();
        proj.ReadFromString(File.ReadAllText(projPath));
        string unityTarget = proj.GetUnityFrameworkTargetGuid();

        #region 系统依赖库

        proj.AddFrameworkToProject(unityTarget, "StoreKit.framework", false);
        proj.AddFrameworkToProject(unityTarget, "iAd.framework", false);

        #endregion

        string content = proj.WriteToString();
        File.WriteAllText(projPath, content);
    }
#endif
}

5.2 手动配置

不写上面代码的话,打包出Xcode工程后,需要手动添加StoreKit.frameworkiAd.framework

然后正常打包进行测试~ 即可完成开篇效果。


六,问题汇总

6.1 示例日志

  • IAP初始化成功日志:

  • 购买成功回调日志:

【Unity IAP】购买成功回调 OnBuySuccess…{“Store”:“AppleAppStore”,“TransactionID”:“1000000866663121”,“Payload”:“MIIT8QYJKoZIhvcNAQcCo
…中间省略N多行…
jSYLAk”}
System.Action`1:Invoke(T)
IAPMgr:ProcessPurchase(PurchaseEventArgs)
UnityEngine.Purchasing.PurchasingManager:ProcessPurchaseIfNew(Product)
UnityEngine.Purchasing.JSONStore:OnPurchaseSucceeded(String, String, String)
System.Action:Invoke()
UnityEngine.Purchasing.Extension.UnityUtil:Update()

6.2 注意事项

  • 真机测试的时候,一定要退出原来的账号(app store 登录的账号退出),才能用沙盒测试账号。
  • 请务必使用真机来测试,一切以真机为准。
  • 项目的Bundle identifier需要与您申请AppID时填写的bundleID一致,不然会无法请求到商品信息。
  • 沙盒环境测试appStore内购流程的时候,请使用没越狱的设备。
  • 沙盒的测试账号和你请求商品信息没有关系。请求商品信息的流程是,你在后台配置好了内购商品,并且将其添加到了需要集成内购功能的App中,然后你请求商品。请求到商品后的流程是这样的,苹果系统会自动弹出登录框让你登录账号。然后根据提示操作进行购买,这里的账号就是你配置的沙盒测试账号。

6.3 参考链接

官方文档 Unity IAP

官方手册 Unity IAP

6.4 文末源码

其实源码以及步骤都在上面分享过了,若还有什么不明白的,可以点击下面链接下载,积分不够的童鞋可以私信我哦~

源码链接


以上是关于Unity接入OneStore内购的主要内容,如果未能解决你的问题,请参考以下文章

Unity 接入IAP(上)Android篇

Unity接入谷歌支付

iOS苹果内购(详细步骤)

Unity3d发布IOS(包含u3d自带IAP内购)的流程-小白篇-Xcode配置发布部分

Unity 之 Mac App Store 内购过程解析(购买非消耗道具 | 恢复购买 | 支付验证)

Unity 之 Mac App Store 内购过程解析(购买非消耗道具 | 恢复购买 | 支付验证)