PlayFab(旧版 Economy)、Unity IAP 和 Android 入门

重要

经济 v1 API 处于维护模式,将不会收到任何新功能,只有 bug 修复。 v1 API 将在可预见的未来进行维护。 请参阅“Economy v2 概述”,详细了解 PlayFab Economy 的下一版本!

本教程介绍如何使用 PlayFab、Unity + IAP 服务和 Android Billing API 设置应用内购买 (IAP)。

开始之前

设置 IAP 可能很乏味,如果不确定不同的服务应该如何集成和协作,就更是如此。

下图说明 Android Billing API 和 PlayFab 如何协作从而为客户提供稳定的 IAP 体验。

Android Billing - PlayFab - 集成时间线

首先通过 PlayMarket 设置产品 ID价格。 开始时,所有产品都面貌不明 - 它们是玩家可以购买的数字实体,但是对于 PlayFab 玩家没有任何意义。

要使这些实体有用,我们需要在 PlayFab 物品目录中对它们进行镜像。 这样,可使“面貌不明”的实体成为捆绑包、容器和独立的物品。

每个物品都具有自己独特的面貌:

  • 游戏
  • 说明
  • 标记
  • 类型
  • 图片
  • 行为

所有这些都通过共享 ID 链接到市场产品。

访问可供购买的实际货币物品的最佳方式是使用 GetCatalogItemsGetStoreItems。 这些是免费货币商店使用的 API 方法,因此,过程是熟悉的。

物品 ID 是 PlayFab 和任何外部 IAP 系统之间的链接。 因此,我们向 IAP 服务传递物品 ID。

此时,购买过程开始。 玩家与 IAP 界面交互 - 如果购买成功 - 则会获得收据。

然后 PlayFab 可以验证收据并注册购买,向 PlayFab 玩家授予其刚刚购买的物品。

这是 IAP 集成的大致工作方式,下面的示例说明了在实际应用中的大部分情况。

设置客户端应用程序

本节介绍如何配置一个非常基本的应用程序,从而测试使用 PlayFab、UnityIAP 和 Android Billing API 的 IAP。

先决条件:

  • 一个 Unity 项目。
  • 已导入 PlayFab SDK 并将其配置为处理游戏。

第一步是设置 UnityIAP:

  1. 导航到 “服务”
  2. 确保选择 Services 选项卡。
  3. 选择 “统一服务” 配置文件或组织。
  4. 选择 “创建” 按钮。

设置 UnityIAP 服务

  1. 接下来,导航到 “应用内购买 (IAP)” 服务。

导航到 UnityIAP 服务

  1. 通过设置 简化跨平台的IAP 切换,请确保启用 “服务”

  2. 然后选择 “继续” 按钮。

启用 UnityIAP 服务

将显示一个插件列表页面。

  1. 选择 “导入” 按钮。

UnityIAP 服务 - 导入插件

继续 Unity 安装和导入过程,直至导入所有插件。

  1. 确认插件是否已就位。
  2. 然后,创建一个名为 AndroidIAPExample.cs 的新脚本。

UnityIAP 创建新脚本

AndroidIAPExample.cs包含下面所示的代码(请参阅代码注释以获得进一步说明)。

using PlayFab;
using PlayFab.ClientModels;
using PlayFab.Json;
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Purchasing;

public class AndroidIAPExample : MonoBehaviour, IStoreListener {
    // Items list, configurable via inspector
    private List<CatalogItem> Catalog;

    // The Unity Purchasing system
    private static IStoreController m_StoreController;

    // Bootstrap the whole thing
    public void Start() {
        // Make PlayFab log in
        Login();
    }

    public void OnGUI() {
        // This line just scales the UI up for high-res devices
        // Comment it out if you find the UI too large.
        GUI.matrix = Matrix4x4.TRS(new Vector3(0, 0, 0), Quaternion.identity, new Vector3(3, 3, 3));

        // if we are not initialized, only draw a message
        if (!IsInitialized) {
            GUILayout.Label("Initializing IAP and logging in...");
            return;
        }

        // Draw menu to purchase items
        foreach (var item in Catalog) {
            if (GUILayout.Button("Buy " + item.DisplayName)) {
                // On button click buy a product
                BuyProductID(item.ItemId);
            }
        }
    }

    // This is invoked manually on Start to initiate login ops
    private void Login() {
        // Login with Android ID
        PlayFabClientAPI.LoginWithAndroidDeviceID(new LoginWithAndroidDeviceIDRequest() {
            CreateAccount = true,
            AndroidDeviceId = SystemInfo.deviceUniqueIdentifier
        }, result => {
            Debug.Log("Logged in");
            // Refresh available items
            RefreshIAPItems();
        }, error => Debug.LogError(error.GenerateErrorReport()));
    }

    private void RefreshIAPItems() {
        PlayFabClientAPI.GetCatalogItems(new GetCatalogItemsRequest(), result => {
            Catalog = result.Catalog;

            // Make UnityIAP initialize
            InitializePurchasing();
        }, error => Debug.LogError(error.GenerateErrorReport()));
    }

    // This is invoked manually on Start to initialize UnityIAP
    public void InitializePurchasing() {
        // If IAP is already initialized, return gently
        if (IsInitialized) return;

        // Create a builder for IAP service
        var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance(AppStore.GooglePlay));

        // Register each item from the catalog
        foreach (var item in Catalog) {
            builder.AddProduct(item.ItemId, ProductType.Consumable);
        }

        // Trigger IAP service initialization
        UnityPurchasing.Initialize(this, builder);
    }

    // We are initialized when StoreController and Extensions are set and we are logged in
    public bool IsInitialized {
        get {
            return m_StoreController != null && Catalog != null;
        }
    }

    // This is automatically invoked automatically when IAP service is initialized
    public void OnInitialized(IStoreController controller, IExtensionProvider extensions) {
        m_StoreController = controller;
    }

    // This is automatically invoked automatically when IAP service failed to initialized
    public void OnInitializeFailed(InitializationFailureReason error) {
        Debug.Log("OnInitializeFailed InitializationFailureReason:" + error);
    }

    // This is automatically invoked automatically when purchase failed
    public void OnPurchaseFailed(Product product, PurchaseFailureReason failureReason) {
        Debug.Log(string.Format("OnPurchaseFailed: FAIL. Product: '{0}', PurchaseFailureReason: {1}", product.definition.storeSpecificId, failureReason));
    }

    // This is invoked automatically when successful purchase is ready to be processed
    public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs e) {
        // NOTE: this code does not account for purchases that were pending and are
        // delivered on application start.
        // Production code should account for such case:
        // More: https://docs.unity3d.com/ScriptReference/Purchasing.PurchaseProcessingResult.Pending.html

        if (!IsInitialized) {
            return PurchaseProcessingResult.Complete;
        }

        // Test edge case where product is unknown
        if (e.purchasedProduct == null) {
            Debug.LogWarning("Attempted to process purchase with unknown product. Ignoring");
            return PurchaseProcessingResult.Complete;
        }

        // Test edge case where purchase has no receipt
        if (string.IsNullOrEmpty(e.purchasedProduct.receipt)) {
            Debug.LogWarning("Attempted to process purchase with no receipt: ignoring");
            return PurchaseProcessingResult.Complete;
        }

        Debug.Log("Processing transaction: " + e.purchasedProduct.transactionID);

        // Deserialize receipt
        var googleReceipt = GooglePurchase.FromJson(e.purchasedProduct.receipt);

        // Invoke receipt validation
        // This will not only validate a receipt, but will also grant player corresponding items
        // only if receipt is valid.
        PlayFabClientAPI.ValidateGooglePlayPurchase(new ValidateGooglePlayPurchaseRequest() {
            // Pass in currency code in ISO format
            CurrencyCode = e.purchasedProduct.metadata.isoCurrencyCode,
            // Convert and set Purchase price
            PurchasePrice = (uint)(e.purchasedProduct.metadata.localizedPrice * 100),
            // Pass in the receipt
            ReceiptJson = googleReceipt.PayloadData.json,
            // Pass in the signature
            Signature = googleReceipt.PayloadData.signature
        }, result => Debug.Log("Validation successful!"),
           error => Debug.Log("Validation failed: " + error.GenerateErrorReport())
        );

        return PurchaseProcessingResult.Complete;
    }

    // This is invoked manually to initiate purchase
    void BuyProductID(string productId) {
        // If IAP service has not been initialized, fail hard
        if (!IsInitialized) throw new Exception("IAP Service is not initialized!");

        // Pass in the product id to initiate purchase
        m_StoreController.InitiatePurchase(productId);
    }
}

// The following classes are used to deserialize JSON results provided by IAP Service
// Please, note that JSON fields are case-sensitive and should remain fields to support Unity Deserialization via JsonUtilities
public class JsonData {
    // JSON Fields, ! Case-sensitive

    public string orderId;
    public string packageName;
    public string productId;
    public long purchaseTime;
    public int purchaseState;
    public string purchaseToken;
}

public class PayloadData {
    public JsonData JsonData;

    // JSON Fields, ! Case-sensitive
    public string signature;
    public string json;

    public static PayloadData FromJson(string json) {
        var payload = JsonUtility.FromJson<PayloadData>(json);
        payload.JsonData = JsonUtility.FromJson<JsonData>(payload.json);
        return payload;
    }
}

public class GooglePurchase {
    public PayloadData PayloadData;

    // JSON Fields, ! Case-sensitive
    public string Store;
    public string TransactionID;
    public string Payload;

    public static GooglePurchase FromJson(string json) {
        var purchase = JsonUtility.FromJson<GooglePurchase>(json);
        purchase.PayloadData = PayloadData.FromJson(purchase.Payload);
        return purchase;
    }
}
  1. 创建名为 “代码” 的新 “游戏” 对象。
  2. 向其添加 AndroidIAPExample 组件 (2 步过程)。
  3. 向它添加 AndroidIAPExample 组件。
  4. 确保保存场景。

UnityIAP 创建示例游戏对象

最后,导航到 “编译设置”

  1. 确认场景是否已添加到 “编译中的场景” 区域。
  2. 请确保已选择 Android 平台。
  3. 转到 “玩家设置” 区域。
  4. 指定 “程序包名称”

注意

请务必提供 自己 的程序包名称,以免造成任何 PlayMarket 冲突。

UnityIAP 添加示例游戏对象

最后,像往常一样生成应用,并确保 APK 安全。

我们目前不会对其进行测试。 首先,我们需要按如下面几节所述,配置 PlayMarket 和 PlayFab。

为 IAP 设置 PlayMarket 应用程序

本节介绍如何为 PlayMarket 应用程序启用 IAP 的具体信息。

注意

设置应用程序本身已超出本教程的范围。 我们已假设 一个应用程序,它配置为至少发布 Alpha 版本。

启用 PlayMarket 应用程序

有用的注意事项:

  • 要达到目的,需要先上传 APK。 请使用我们在上一节中构建的 APK。
  • 当系统提示上传 APK 时,可以将它上传为 Alpha 或 Beta 应用程序从而启用 IAP 沙盒。
  • 配置 Content Rating 将涉及有关如何在应用程序中启用 IAP 的问题。
  • PlayMarket 允许发布者使用或测试 IAP,因此,请选取另一个 Google 帐户进行测试,并将其添加为 Alpha/Beta 版本的测试人员。

应用程序版本发布后:

  1. 从菜单中选择 In-app products
    • 如果提示提供 Merchant Account,请链接或创建一个。
  2. 选择 Add New Product 按钮。

PlayMarket 添加新产品

  1. 在下面显示的屏幕上,选择 “托管产品”.。
  2. 为它提供描述性 产品 ID
  3. 选择继续

PlayMarket 添加产品 ID

PlayMarket 要求填写 Title (1)Description (2)。 不过,在我们的示例中,不太会用到它们。

我们将仅从 PlayFab 服务获取 Data Item 数据,并且只需 ID 匹配。

PlayMarket 添加产品名称描述

  1. 进一步滚动并选择 Add a price 按钮。

PlayMarket 添加产品价格

  1. 输入有效的价格(注意价格是如何针对每个国家/地区/区域独立转换的)。
  2. 选择 Apply 按钮。

PlayMarket 添加产品应用本地价格

  1. 最后,滚动回到屏幕顶部,将物品的状态更改为 “可用”

PlayMarket 使产品激活

尽管这样就结束了应用的配置,我们还是需要再进行一些优化。 首先,我们保存 Licensing Key(这样便于链接 PlayFab 与 PlayMarket)。

  1. 导航到菜单中的 Services & APIs
  2. 然后,找到并保存 “密钥”Base64 版本。

PlayMarket 保存产品许可密钥

下一步是启用 IAP 测试。 尽管 Alpha 和 Beta 版本自动启用了沙盒,我们还是需要设置获得授权可以测试应用的帐户:

  1. 导航到“主页”
  2. 在左侧菜单中查找并选择 “帐户详细信息”
  3. 找到 License Testing 区域。
  4. 验证 测试账户 是否在列表中。
  5. 请确保 许可证测试响应 设置为 RESPOND_NORMALLY

别忘记应用设置!

PlayMarket 启用 IAP 测试

现在,集成的 Play Market 端的设置就完成了。

设置 PlayFab 游戏

最后一步是配置 PlayFab 作品,以反映我们的产品,并与 Google Billing API 集成。

  1. 选择 “附加内容”
  2. 然后选择 Google 加载项。

PlayFab 打开 Google 加载项

  1. 填入 程序包 ID
  2. 填写在上一节中获取的 Google 应用许可证密钥
  3. 通过选择 Install Google 按钮提交更改。

PlayFab 安装 Google 加载项

下一步是在 PlayFab 中反映我们的 Golden Sword 物品:

  1. 选择 Economy
  2. 确认选择了 Catalogs 子选项卡。
  3. 选择 “新目录”
  4. 提供 “目录版本” 名称。
  5. 选择 “保存目录”

PlayFab 保存目录

如果目录没有物品,则会将其 自动 删除。 这就是为什么所有新目录都带一个名为 One 的预添加物品。

我们可以随时 创建 物品,但为了清楚起见,我们修改现有的 One 物品。

  1. 选择该项目来编辑 项目 ID,使之与 PlayMarket 中的 ID 相匹配。
  2. 现在,编辑“显示名称”,并添加 “说明”。

注意

请注意,这些数据与 Play Market Item TitleDescription无关 - 是完全 独立的。

  1. 项目 指定 价格
  2. 选择 “保存项目“ 以提交更改。

PlayFab 编辑并保存目录物品

注意

在本教程中,IAP 主要指实际货币购买。 这就是为什么我们会使用 RM - 特殊的实际货币。 PlayFab 金额是以美分为单位确定的。

  1. Item ID 列表中观察物品。

PlayFab 目录物品列表

PlayFab 作品的设置到此结束。

测试

为了进行测试,请下载使用 Alpha/Beta 版本的应用。

  • 请务必使用测试帐户和真实 Android 设备。
  • 启动应用后,应该看到 IAP 初始化,以及一个表示物品的 按钮
  • 选择该按钮。

测试应用 - Buy Golden Sword 按钮

将启动 IAP 购买。 按照 Google Play 说明操作直至购买成功。

测试应用 - Google Play - 付款成功

最后,在 PlayFab “游戏管理器” 仪表板中导航到作品,找到 “新事件”

Game Manager 仪表板新事件

这意味着已成功将购买提供、验证和传送到 PlayFab 生态系统。

现在,已成功将 UnityIAP 和 Android Billing API 集成到 PlayFab 应用程序中。