コマース

【PlayFab】アプリ内課金(IAP)を統合する方法

playfab-unity-iap

Unity のモバイルアプリで課金アイテムを販売するなら、アプリ内課金(IAP)の実装が必須になります。

例えば、「ジェム50個を1,000円で販売する」といったシーンや、「1ヶ月間だけ持続するアイテムを販売する」ということにも使えます。

しかし、IAP をゼロから実装するには難易度が高く、上級者向けと言えるでしょう。

PlayFab を使えば、レシート検証をやってくれるだけでなく、インベントリへの連携も簡単にしてくれます。

わりと苦戦しましたが、なんとかひと通り実装できたので、惜しみなくノウハウを公開しました。

ロジック部分については、ほぼコピペで流用できるはずです。

今回の動作確認は iOS で実施しました。

Android では動作確認できていませんが、おそらく問題なく実行できるはずです。

では、さっそく見ていきましょう。

アプリ内課金(IAP)を統合する方法

IAP の一般的な準備について解説していると、それだけで2記事くらいになるので、この記事で解説しません。

主にプログラム側を中心に解説してきます。

事前準備(Android)

Android の準備については、公式ドキュメント「PlayFab、Unity IAP、および Android の概要」を参照してください。

少し画像が古い箇所もありますが、特に迷うことなく準備できると思います。

事前準備(iOS)

iOS については、以下の素晴らしい記事を参照することでひと通り準備ができます。

【Apple】iOSアプリ公開までに行ったことその1[AppleID〜Sandbox]備忘録

注意点としては、口座情報や税金フォームも登録しないと正常に動作しません。

あとは、PlayFab 側で Apple のアドオンを設定する必要があります。

アドオンの設定は、Sign in with Apple を「簡単に」統合する方法の事前準備を参照してください。

アイテムは以下を用意しました。

playfab-unity-iap

アイテムの準備

PlayFab 側で課金アイテムの準備をしていきます。

アイテムについて詳しく知りたい人は、ストアのアイテムを一覧表示して購入する手順をご覧ください。

今回は以下の3つを用意しました。

  • 消費型 カウント(Consumable)
  • 永続型(NonConsumable)
  • 消費型 時間(Subscription)
playfab-unity-iap

1つずつ消費したいアイテムについては、「消費型」で登録します。

あとで絞り込みをするために、アイテムクラスに「Consumable」を設定しました。

playfab-unity-iap

PlayFab 側でも価格の設定がありますが、Android または iOS 側で登録された値段が優先されるようになっています。

なので、PlayFab 側は何円になっていても関係ありません。

playfab-unity-iap

わかりやすいように合わせておくとわかりやすいですが、今回は RM 100 としました。

PlayFab では「RM を 100 で割った数値」がリアルマネーのドルとして計算されます。

なので、RM 100 というのは 1ドル のことですね。

RM 99 とすると、0.99 ドルになります。

続いて、1度買ってしまえばいいものは、「永続型」として登録します。

アイテムクラスは「NonConsumable」として登録しました。

playfab-unity-iap

最後に、一定期間が経過したらなくなるアイテムを作りたい場合は、消費型で時間を設定します。

アイテムクラスは「Subscription」として登録しました。

playfab-unity-iap

アイテムの準備はこれで完了です。

プログラムを書く

プログラムの全体は、私が作った IAPExample.cs を参照してください。

少しわかりにくいので、まずはアイテム購入の全体像を見てみましょう。

playfab-unity-iap

最後の「インベントリへの反映」は PlayFab が自動でやってくれるので、全体としては4つの手順で購入することができるようになっています。

1つずつ見ていきますね。

カタログからアイテム一覧を取得

まずは、PlayFab に登録しているアイテムを取得します。

private List<CatalogItem> Catalog;

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

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

取得したカタログは後で使うので、変数として保持しておきます。

ここは特に難しいところはないですね。

最後に、Unity IAP の初期化処理を呼び出しています。

Unity IAP の初期化

次に、Unity IAP の初期化をしていきます。

ConfigurationBuilder builder;

public void InitializePurchasing()
{
    if (IsInitialized) return;

    builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());

    // Register each item from the catalog
    foreach (var item in Catalog.FindAll(x => x.ItemClass == "Consumable"))
        builder.AddProduct(item.ItemId, ProductType.Consumable);

    foreach (var item in Catalog.FindAll(x => x.ItemClass == "NonConsumable"))
        builder.AddProduct(item.ItemId, ProductType.NonConsumable);

    foreach (var item in Catalog.FindAll(x => x.ItemClass == "Subscription"))
        builder.AddProduct(item.ItemId, ProductType.Subscription);

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

IAP のお作法は、Unity IAP の公式ドキュメントが参考になると思います。

builder に商品を追加して初期化をしなければならないのですが、アイテムの種類ごとに追加が必要になります。

ここで、先ほど付けておいたアイテムクラスが活きてくるんですね。

取得したカタログから、アイテムクラスごとに抽出してあげれば、簡単に追加することが可能です。

最後に、IAP の初期化メソッドを呼び出しています。

で、初期化に成功した場合は OnInitialized 、失敗した場合は OnInitializedFailed が呼ばれる仕組みです。

private IStoreController storeController;
private IExtensionProvider extensionProvider;

public bool IsInitialized
{
    get => storeController != null && extensionProvider != null && Catalog != null;
}

public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
{
    storeController = controller;
    extensionProvider = extensions;
}

public void OnInitializeFailed(InitializationFailureReason error)
{
    Debug.Log("OnInitializeFailed InitializationFailureReason:" + error);
}

初期化されたかどうかは、各変数が null かどうかで判断するようにしています。

初期化についてはこんなところですね。

アイテムを購入

次に、実際にアイテムを購入する手順です。

void BuyProductID(string productId)
{
    if (!IsInitialized)
        throw new Exception("IAP Service is not initialized!");

    storeController.InitiatePurchase(productId);
}

InitiatePurchase を呼び出すことで、iOS や Android に搭載されている機能で購入処理を進めることができます。

productId には、PlayFab 側のアイテム ID を渡してあげます。

iOS と Android のストアでも同じ ID で登録している前提なので、どちらでも正常に動作するはずです。

購入の結果は、ProcessPurchase で受け取ることができます。

public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs e)
{
    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);

    // ----------
    // -- 中略 --
    // ----------
}

「購入した」という情報は、やろうと思えば改ざんすることもできます。

実際に購入していないのに、有料のアイテムをたくさん手に入れられる、といった不正は防がないといけません。

そこで用意されている機能が、次で紹介する「レシート検証」です。

レシート検証

レシート検証は自前で実装すると大変ですが、ありがたいことに PlayFab 側で API が用意されています。

iOS の検証 Validate IOS Receipt
Android の検証 Validate Google Play Purchase

つまり、メソッドの引数に適切な値を渡してあげるだけで、いろいろとサーバー側で検証してくれるということです。

実際のコードは以下の通り。

public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs e)
{
    // ----------
    // -- 中略 --
    // ----------
#if UNITY_IOS
    var wrapper = (Dictionary<string, object>)MiniJson.JsonDecode(e.purchasedProduct.receipt);
     
    var store = (string)wrapper["Store"];
    var payload = (string)wrapper["Payload"]; // For Apple this will be the base64 encoded ASN.1 receipt

    PlayFabClientAPI.ValidateIOSReceipt(new ValidateIOSReceiptRequest
    {
        CurrencyCode = e.purchasedProduct.metadata.isoCurrencyCode,
        PurchasePrice = (int)e.purchasedProduct.metadata.localizedPrice * 100,
        ReceiptData = payload
    }, result => {
        Debug.Log("Validation successful!");
    },
        error => {
            Debug.Log("Validation failed: " + error.GenerateErrorReport());
        }
    );

#elif UNITY_ANDROID

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

    PlayFabClientAPI.ValidateGooglePlayPurchase(new ValidateGooglePlayPurchaseRequest()
    {
        CurrencyCode = e.purchasedProduct.metadata.isoCurrencyCode,
        PurchasePrice = (uint)(e.purchasedProduct.metadata.localizedPrice * 100),
        ReceiptJson = googleReceipt.PayloadData.json,
        Signature = googleReceipt.PayloadData.signature
    }, result =>
    {
        Debug.Log("Validation successful!");
    },
        error =>
        {
            Debug.Log("Validation failed: " + error.GenerateErrorReport());
        }
    );
#endif
    return PurchaseProcessingResult.Complete;
}
PurchasePrice に ×100 をしているのは何で?

上の方でも書きましたが、PlayFab では「RM を 100 で割った数値」がリアルマネーのドルとして計算されます。

なので、RM 100 というのは 1ドル のことですね。

つまり、価格を RM に変換しているというわけです。

ペンギンくん
ペンギンくん
なるほど、わかりにくい。
ねこじょーかー
ねこじょーかー
その辺が詳しく解説されているわけじゃないから、読み取るのが大変…

あとは、PurchaseEventArgs の中身に詰まっている情報を渡してあげればOKです。

余談ですが、iOS と Android の PurchasePrice の型が、int と uint で違っているのが謎すぎます。

レシート検証が無事に通れば、自動的にインベントリにアイテムが入るという仕組みです。

これでひと通り実装が完了しました。

動作確認

動作確認前に iPhone からサインアウトが必要です。

実行して左上のボタンを押すことで、アイテムを購入できます。

ボタンを押すと、サインインの画面が出るので、サンドボックス用のアカウントでログインします。

起動画面
playfab-unity-iap
サインイン
playfab-unity-iap

購入するアイテムによって、アイテム名と金額が変わることがわかります。

アイテムの細かい説明は表示されないようですね。

消耗品
playfab-unity-iap
非消耗品
playfab-unity-iap
サブスクリプション
playfab-unity-iap

ここで「購入する」を押すと、無事に購入が完了します。

画面中央に「Validation Successfull!」が表示されたら正常に完了した証拠です。

完了メッセージ
playfab-unity-iap
レシート検証
playfab-unity-iap

以下は PlayStream のログです。

playfab-unity-iap

また、「プレイヤー > 購入」と進んでいくと、購入履歴の確認も可能です。

playfab-unity-iap

PlayFab ではドル表記になるので、円から逆算された RM が価格として入っていることがわかりますね。

これで、ひと通りの動作を確認することができました。

実際の運用はどうすべきか

ここで少し踏み込んで、実際の運用ではどうすべきかについて考えていきます。

ユーザーがリアルマネーを使うということは、一番バグがないよう気をつけないといけない部分になりますよね。

「課金したのにアイテムが反映されない」などのトラブルになると、ゲームの存続に関わる可能性が高いです。

トラブルのリスクを少しでも減らすため、「できるだけ課金をする回数を減らしてもらう工夫」が必要になると思います。

例えば、以下のような感じですね。

  1. リアルマネーでしか買えない仮想通貨を買ってもらう
  2. その仮想通貨を使用して、専用アイテムを購入してもらう

こうしておけば、以下のように変わります。

playfab-unity-iap

アイテムに仮想通貨を入れて販売するには、バンドルを使うことで実現できます。

バンドルについては、ストアでバンドルを売る手順を参照してください。

最後に

Unity IAP と PlayFab を連携する方法について解説してきました。

IAP は実機でしか動作確認ができないので、毎回ビルドするのがわりと面倒でした。

アプリ開発の終盤になると、容量が増えてビルド時間が長くなるので、容量が小さい最初のうちに実装するのもありかもしれません。

この記事を参考に、みなさんが IAP を統合する助けになれば幸いです。

オンライン書籍「猫でもわかる PlayFab 入門」
playfab-book

日本で初めて、PlayFab の参考書を発売しました。

100時間以上の学習内容を凝縮した参考書です。入門として必要な知識は、すべてこの本に記載しました。

 

COMMENT

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です