はなちるのマイノート

Unityをメインとした技術ブログ。自分らしくまったりやっていきたいと思いますー!

【Unity】Unity IAPのCodeless IAPを用いて非消費型(買い切り型)のアプリ内課金を実装する(iOS・Android)

はじめに

今回はUnity IAPCodelessIAPという機能を用いて、簡単に非消費型(買い切り型)のアプリ内課金を実装してみるという記事になります。

docs.unity3d.com

先に言っておくと、Unityでの実装はすごく簡単ですが割とストア関連・テスト関連で躓くことの方が多いかもしれません。

Unity IAP SDKのインストール

Windos -> General -> ServiceからServiceウィンドウを立ち上げます。

f:id:hanaaaaaachiru:20220201231842p:plain
Serviceウィンドウを開く

In-App PurchasingONにします。

f:id:hanaaaaaachiru:20220201233056p:plain
In-App Purchasingを有効にする

プロジェクトIDを作成していない場合

プロジェクトIDをまだ作成していなかった場合は以下の操作を行う必要があります。

組織・プロジェクトIDをリンクさせ、COPPAコンプライアンスという13歳未満を対象としているかどうかを選択します。

f:id:hanaaaaaachiru:20220202134317p:plain
プロジェクトIDの作成




無事に以下の画像の画面が開けたら、Welcomeという欄の中にImportボタンがあるので、こちらからPluginsをインストール。

f:id:hanaaaaaachiru:20220201234214p:plain
Importを実行する

するとPlugins/UnityPurchasingがインポートされているはずです。

実装

IAP Buttonsを配置

Hierarchy上で右クリックをし、Unity IAP -> IAP Buttonを選択し、IAP ButtonコンポーネントがアタッチされたButtonを作成します。

f:id:hanaaaaaachiru:20220202135954p:plain
IAP Button生成

今回実装するのは広告削除・追加コンテンツといった買い切り型のアプリ内課金なので、以下の二つのボタンが必要になります。

  • Non-Consumable(非消費型)Button:購入をするボタン
  • Restore Button:購入データ復元のボタン(Androidの場合は不要だが、iOSの場合は必要)
f:id:hanaaaaaachiru:20220202140928p:plain
ボタンの実装サンプル

IAP CatalogでProductsを定義

IAP Buttonコンポーネントから、IAP Catalog...というボタンを押し、IAP Catalog Windowを開きます。

f:id:hanaaaaaachiru:20220202141311p:plain
IAP Catalog

プロダクトの定義の仕方については以下の公式ドキュメントを参照してみてください。
docs.unity3d.com

ざっと要点をまとめてみるとこんな感じ。

  • IDcom.<company name>.<game name&>.<product name>のように表記します。文字は全て小文字で表記。(例. com.hanachiru.novelgame.removeads)
  • TypeNon Consumable
  • Google ConfigurationApple ConfigurationPriceを設定(GooglePriceは日本円,Appleは選択する)
  • Automatically inititalize Unity Purchasingを有効にする
f:id:hanaaaaaachiru:20220202160328p:plain
実際に打ち込んだ様子

※ 画像では誤ってProduct IDに大文字を含んでしまっていますが、全て小文字にしてください。
※ GooglePlayのPriceはドル表記ではなく、日本円表記で記載するみたいでした

またiOSPriceと日本円の対応は以下になるそうです。(2022/2/4時)

f:id:hanaaaaaachiru:20220204002630p:plainf:id:hanaaaaaachiru:20220204002632p:plain
対応表

IAP Buttonにプロダクトを設定する

購入用のIAP Buttonをインスペクターからいじります。

  • Product IDの設定
  • ButtonTypePurchase
  • Title TextDescription TextPrice Textを用いるとIAP Catalogで設定したものを反映させることができる(別に使わなくても良い)
f:id:hanaaaaaachiru:20220202161554p:plain
購入するIAP Button


次にRestore用のIAP Buttonのインスペクターを設定。

  • Button TypeRestoreに設定
f:id:hanaaaaaachiru:20220202162023p:plain
Restore Buttton

スクリプトを実装する

IAP Buttonが押され、購入成功・失敗したときの処理を実装します。

public class IAPManager : MonoBehaviour
{
    [SerializeField] private IAPButton buyButton;

    private void Awake()
    {
        // 購入ボタンを押されたとき、成功・失敗したときのイベントを登録する
        buyButton.onPurchaseComplete.AddListener(OnPurchaseComplete);
        buyButton.onPurchaseFailed.AddListener(OnPurchaseFailed);
    }

    private void OnDestroy()
    {
        // イベント解除
        buyButton.onPurchaseComplete.RemoveListener(OnPurchaseComplete);
        buyButton.onPurchaseFailed.RemoveListener(OnPurchaseFailed);
    }

    private static void OnPurchaseComplete(Product product)
    {
        // 購入に成功した時とき
        Debug.Log("Remove Ads");
    }

    private static void OnPurchaseFailed(Product product, PurchaseFailureReason failureReason)
    {
        // 購入処理に失敗したとき
        Debug.Log($"{product.definition.id} failed : {failureReason}");
    }
}
f:id:hanaaaaaachiru:20220202170317p:plain
IAP Manager

正しく設定をすると、以下のようにフェイクの購入画面が立ち上がるはずです。

f:id:hanaaaaaachiru:20220202170838g:plain
フェイクの購入画面

Google Playで公開される場合はRestoreボタンを非表示にする

iOSの場合はRestore用のボタンが必須ですが、逆にAndriodの場合は表示しないようにしないといけません。(Andriodの場合は起動時に自動でRestoreされる)

コードを少し追加します。

public class IAPManager : MonoBehaviour
{
    [SerializeField] private IAPButton buyButton;
    [SerializeField] private IAPButton restoreButton;

    private void Awake()
    {
        // Androidの場合はRestore用のボタンを非表示にする
        if (Application.platform != RuntimePlatform.IPhonePlayer)
        {
            restoreButton.gameObject.SetActive(false);
        }
        
        buyButton.onPurchaseComplete.AddListener(OnPurchaseComplete);
        buyButton.onPurchaseFailed.AddListener(OnPurchaseFailed);
    }

    private void OnDestroy()
    {
        buyButton.onPurchaseComplete.RemoveListener(OnPurchaseComplete);
        buyButton.onPurchaseFailed.RemoveListener(OnPurchaseFailed);
    }

    private static void OnPurchaseComplete(Product product)
    {
        // 購入に成功した時とき
        Debug.Log("Remove Ads");
    }

    private static void OnPurchaseFailed(Product product, PurchaseFailureReason failureReason)
    {
        // 購入処理に失敗したとき
        Debug.Log($"{product.definition.id} failed : {failureReason}");
    }
}
f:id:hanaaaaaachiru:20220202171544p:plain
Restoreのボタンを紐づける

ストアの設定

Google Play Consoleで設定を行う

まずGoogle Playに提出するGooglePlayProductCatalog.csvを生成します。

IAP Catalogを立ち上げ、App Store Export -> Google Play CSVを選択。

f:id:hanaaaaaachiru:20220202215850p:plain
Google Play CSVを生成

無事にファイルが出力できたら、Google Play Consoleを開いてください。

対象アプリ(まだ作成していない場合は新規作成)の商品 -> アプリ内アイテムを選択し、設定を行います。

また以下のようにアイテム設定ができない場合は、必要情報を記入、アプリをビルドしてクローズドテストとして公開等すればいけるはずです。

f:id:hanaaaaaachiru:20220203223609p:plain
必要情報が足りない場合


無事に以下のような画面が表示されたら、作成したGooglePlayProductCatalog.csvをアップロードすれば完了です。

f:id:hanaaaaaachiru:20220204002854p:plain
アプリ内アイテム
f:id:hanaaaaaachiru:20220204002944p:plain
csvをインポート
f:id:hanaaaaaachiru:20220204003045p:plain
アイテムが読み込まれている

App Store Connectで設定を行う

App Store ConnectApp内課金 -> 管理からApp内課金の項目を追加していきます。

  • App内の課金のタイプは非消費型
  • 参照名は自由に
  • 製品IDはIAP Catalogに書いたIDcom.<company name>.<game name&>.<product name>
  • 価格はIAP Catalogに記載した値段
  • App情報はストアの表示名
  • スクリーンショットを添付
f:id:hanaaaaaachiru:20220204003335p:plain
無事アイテムを定義できた様子

テスト課金

Andriod

Google Play Consoleのダッシュボードを見てみると、クローズドテストに関するToDOリストがあるはずです。(なければクローズドテストにaab(apk)をアップロードすれば出てくるはず)

f:id:hanaaaaaachiru:20220203234848p:plain
TODOリスト

こちらのタスクをまずはこなしてください。

テスターを指定すれば、対象者はアプリをダウンロードできますが、テスト課金でなく実際に課金されてしまうことに注意してください。

f:id:hanaaaaaachiru:20220204004140p:plain
テスター指定

課金テストをする場合は、すべてのアプリが見える画面にて設定 -> ライセンス テストを選択、対象者のメールアドレスを記載すればOKです。

f:id:hanaaaaaachiru:20220204004533p:plain
課金テストができるユーザーを選択する

iOS

App Store Connect契約 / 税金 / 口座情報にて口座情報等を設定していない場合は設定します。(私はこれを忘れてずっと悩んでました)

App Store Connectを開き、上のメニューバーにあるユーザーとアクセスを選択。

f:id:hanaaaaaachiru:20220207023453p:plain
ユーザーとアクセス

sandbox -> テスターを選択し、プラスマークよりテスターを追加します。

f:id:hanaaaaaachiru:20220207023721p:plain
テスターの追加

UnityでiOSビルドを行い、xcodeprojを開きます。

あとはお手元のiPhoneにビルドを行い、テスターとして設定したアカウントにて購入を行えばOKです。

(補足) レシート検証

Codeless IAPではレシート検証が行われないとのことなので注意してください。(必ずやらなければならないわけではないです)

forum.unity.com

docs.unity3d.com

今回は例として、CrossPlatformValidatorを用いた簡易なローカル検証を実装してみます。

難読化

メニューバーよりWindow -> Unity IAP -> IAP Receipt Validation Obfuscatorを選択。

f:id:hanaaaaaachiru:20220222104143p:plain
Receipt Validation Obfuscator

提示されている手法を上から実行していきます。

  • Google Play Consoleを開き、対象アプリを選択、収益化のセットアップのライセンスからキーをコピー
f:id:hanaaaaaachiru:20220222105200p:plain
キーの取得
  • Receipt Validation Obfuscatorにキーをペースト
f:id:hanaaaaaachiru:20220222105550p:plain
Google Play License Keyをペースト
  • Obfuscate Google Play License Keyをクリック
  • Open Analytics Dashboardからリンクを飛び、Google Play License Keyをペースト
f:id:hanaaaaaachiru:20220222105751p:plain
Unity Dashboradにキーをペースト

購入処理のコードに追記する

公式ドキュメントにサンプルコードがあるので、そちらに少し修正を加えて利用させていただきます。

private static void OnPurchaseComplete(Product product)
{
    bool validPurchase = true; // R.V. のないプラットフォームに有効です

    // Unity IAP の検証ロジックはこれらのプラットフォームにのみ含まれます。
# if UNITY_ANDROID || UNITY_IOS || UNITY_STANDALONE_OSX
    // エディターの難読化ウィンドウで準備した機密を持つ
    // バリデーターを準備します。
    var validator = new CrossPlatformValidator(GooglePlayTangle.Data(),
        AppleTangle.Data(), Application.identifier);

    try {
        // Google Play で、result は 1 つの product ID を取得します
        // Apple stores で、receipts には複数のプロダクトが含まれます
        var result = validator.Validate(product.receipt);
        // 情報提供の目的で、ここにレシートをリストします
        Debug.Log("Receipt is valid. Contents:");
        foreach (IPurchaseReceipt productReceipt in result) {
            Debug.Log(productReceipt.productID);
            Debug.Log(productReceipt.purchaseDate);
            Debug.Log(productReceipt.transactionID);
        }
    } catch (IAPSecurityException) {
        Debug.Log("Invalid receipt, not unlocking content");
        validPurchase = false;
    }
# endif

    if (validPurchase)
    {
        // 購入に成功した時とき
        Debug.Log("Remove Ads");
    }
}

具体的にはサンプルの以下の箇所を変更しています。

  • Application.bundleIdentifierは廃止されたようなので、Application.identifierに書き換え
  • e.purchasedProduct.receiptproduct.receiptに書き換え

またこのコードではレシートが有効であるかどうかの確認しかしていないので、レシートの中身を検証したいなどありましたら公式ドキュメント等を参照してください。
レシート検証 - Unity マニュアル