0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【日本語解説】Unity In-App Purchasing v5

0
Posted at

概要

Unity In-App Purchasing(IAP)v5 は v4 からのメジャーアップデートで、初期化フロー・商品取得・購入処理・購入確定(Confirm)・レシート検証の考え方が大きく変わりました。

この記事では、v4 の UnityPurchasing.Initialize / IStoreListener 中心の実装から、v5 の StoreController / ProductService / PurchaseService による段階的な購入フローへ移行する際の要点(何が変わったか、どこを直すべきか、Confirm とサーバー検証をどう設計するか)をまとめました。

この記事をまとめた背景として、Unity Discussionsの記事より、IAP 4 のサポート終了と IAP 5 への移行推奨について以下のように案内されています。
Unity IAP 4: End of Support Announcement(英語記事より翻訳して引用)

2026年6月8日付で Unity IAP 4 は非推奨(deprecated)となり、今後は IAP 5 が移行先になります。
2026年6月8日以降、IAP 4 には新機能追加・バグ修正・カスタマーサポートが提供されず、Package Manager 上でも deprecated として表示され始めます。
ただし、既存の IAP 4 プロジェクトが即座に動作しなくなるわけではなく、パッケージ自体も削除されません。
Unity 6.3 より後の次期 LTS(2026年12月頃想定)では、エディターに同梱されるパッケージ解決のデフォルトが IAP 4 ではなく IAP 5 を指す予定です。
そのため、既存の IAP 4 プロジェクトでも、次の開発サイクルで IAP 5 への移行を計画することが推奨されています。

そのため、IAP 4 はすぐに使用不可になるわけではありませんが、2026年6月8日以降は新機能・修正・サポートが止まり、次期 LTS 以降では IAP 5 がデフォルト解決される見込みであるため、継続運用するプロジェクトでは IAP 5 への移行計画が必要です。


主要な変更点

Unity In-App Purchasing(IAP)v5 は、v4 からのメジャーアップデートとして、初期化・商品取得・購入処理・購入確定・レシート取得/検証まわりの API と責務分担が大きく変更されています。

v5 では、従来の UnityPurchasing.Initialize / IStoreListener 中心の実装から、StoreController のイベントと非同期処理を使って、ストア接続・商品取得・購入取得・保留中購入処理を明示的に制御する構成へ移行します。

1. アーキテクチャの変更

v4 では UnityPurchasing.Initialize と IStoreListener を中心に、初期化・商品取得・購入イベントをまとめて扱っていました。

v5 では責務が分離され、主に以下のサービスを使う構成になります。

  • StoreController
    • ストア接続
    • 商品取得
    • 購入開始
    • 購入完了確認
  • ProductService
    • 商品情報の取得
  • PurchaseService
    • 購入処理・購入状態の監視

これにより、購入フローの各ステップをより細かく制御できます。

2. 初期化フローの変更

v4 では ConfigurationBuilder を作成し、UnityPurchasing.Initialize(listener, builder) を呼ぶ形でした。

v5 では、概ね以下の流れになります。

  1. StoreController を生成
  2. 購入関連イベントを登録
  3. ストアへ接続
  4. 商品情報を取得
  5. 保留中の購入を処理

v5 では「初期化時にストア接続・商品取得・購入取得までまとめて完了する」構成ではなく、接続・商品取得・購入取得を分けて実行し、それぞれの成功/失敗イベントを処理する実装へ変わります。

3. 商品定義・商品取得の変更

v4 では ConfigurationBuilder.AddProduct(...) で商品を登録していました。

v5 では、ProductDefinition / ProductFetchRequest などを使って商品情報を取得します。

影響点:

  • 商品 ID と商品タイプの定義方法が変わる
  • 商品一覧の取得処理を明示的に呼ぶ必要がある
  • 取得成功・失敗時のハンドリングを v5 のイベント/API に合わせて書き換える必要がある

4. 購入処理の変更

v4 では IStoreController.InitiatePurchase(product) を使っていました。

v5 では StoreController.PurchaseProduct(...) など、v5 の購入 API に置き換わります。

また、購入結果の受け取り方も ProcessPurchase ベースから、v5 の購入イベント・購入状態に合わせた実装へ変更されます。

5. 購入完了処理の変更

v4 では ProcessPurchase の戻り値として PurchaseProcessingResult.Complete / Pending を返すことで、購入完了または保留を表現していました。

v5 では、購入を完了させるために 明示的に Confirm 処理を行う形になります。

重要な考え方:

  • サーバー検証やアイテム付与が完了するまでは購入を Confirm しない
  • 検証・付与が成功した後に Confirm する
  • 未 Confirm の購入は pending purchase として扱われる

そのため、従来 ProcessPurchase 内で完結していた処理は、v5 では 購入受信 → レシート検証 → サーバー反映 → Confirm という流れに整理する必要があります。


レシート検証まわりの変更

1. レシート取得元が Product.receipt から Order.Info.Receipt へ変わる

Unity のアップグレードガイドでは、IAP v5 でレシートを取得する場合、従来の Product.receipt ではなく、Order.Info.Receipt を使うよう案内されています。

また、Apple App Store のレシート検証は deprecated とされており、Apple では StoreKit 2 の jwsRepresentation を使う方向が示されています。

2. サーバーサイド検証が推奨

IAP v5 では、購入処理を受け取ったあと、必要に応じて Order.Info.Receipt や Apple の OrderInfo.Apple.jwsRepresentation など、ストアごとに必要な情報を使って検証します。

有償通貨やアイテム付与をサーバー側で管理している場合は、クライアントで受け取った購入情報をサーバーへ送信し、検証と付与を完了してから購入を Confirm する構成が安全です。

推奨フロー:

  1. Unity IAP v5 で購入完了イベントを受け取る
  2. 購入情報・レシート情報を自社サーバーへ送信
  3. サーバー側で Apple / Google に問い合わせて検証
  4. 検証成功後、サーバー側でアイテム・通貨・サブスク状態などを反映
  5. クライアント側で購入を Confirm

3. Apple / Google で扱うレシート情報に注意

Unity のアップグレードガイド上では、ストアごとに扱う情報が異なります。

  • Google
    • Google Play のレシート検証では Order.Info.Receipt を使用
  • Apple
    • Apple App Store の従来のレシート検証は deprecated
    • StoreKit 2 の jwsRepresentation を使う方向が示されている
    • Unity Discussion では、StoreKit 1 を利用するケースでは JWS ではなくレシートアクセスが必要になる旨も補足されている

つまり、v5 移行時は「v4 と同じレシート JSON をそのままサーバーへ投げれば十分」とは限らず、各ストアで必要な値・検証方式を確認する必要があります。


Unity Discussion の補足ポイント

Unity Discussion のアップグレードチュートリアルでは、v4 から v5 にパッケージを更新すると、型定義や interface の廃止により コンパイルエラーが発生することが説明されています。

主な示唆:

  • 単純なパッケージ更新だけでは移行できない
  • IStoreListener / IStoreController / ConfigurationBuilder 前提の実装は大きく書き換えが必要
  • 初期化、商品取得、購入、購入完了確認を段階的に v5 API へ置き換える必要がある
  • 既存の独自 IAP Manager 実装では、UnityIAPServices.StoreController() を中心にした実装へ移行する必要がある

既存 InAppPurchase Manager への影響

影響が大きい箇所

既存の InAppPurchase Manager が v4 ベースの場合、特に以下の箇所が影響を受けます。

  • IStoreListener の実装
  • UnityPurchasing.Initialize(...)
  • ConfigurationBuilder.AddProduct(...)
  • IStoreController / IExtensionProvider
  • ProcessPurchase(...)
  • OnPurchaseFailed(...)
  • PurchaseProcessingResult.Pending / Complete
  • CrossPlatformValidator
  • レシート JSON の取り扱い
  • サーバー側の check_receipt API に渡すパラメータ

改修方針

1. IAP Manager の責務を整理する

v5 では、以下の責務を明確に分けると実装しやすくなります。

  • IAP 初期化・ストア接続
  • 商品情報取得
  • 購入開始
  • 購入成功・失敗イベント処理
  • レシートをサーバーへ送信
  • サーバー検証結果の反映
  • 購入 Confirm
  • pending purchase の再処理

2. 購入完了はサーバー検証後に Confirm する

アイテム付与や通貨反映がサーバー側で行われるプロジェクトでは、以下の順序を守るのが安全です。

3. レシート形式の差分を確認する

既存サーバーが v4 のレシート JSON を前提にしている場合、v5 で取得できる購入情報・レシート情報と互換性があるか確認が必要です。

確認ポイント:

  • Product ID
  • Transaction ID / Order ID
  • Store 名
  • Receipt / Payload
  • Signature
  • Apple / Google それぞれの検証に必要な値
  • サンドボックス環境でのレスポンス差分

実装の改修例

以下は、v4 ベースの実装から v5 ベースの実装へ移行する際に、特に変わる箇所を比較するためのサンプルです。

実際の API 名や型は利用する Unity IAP v5 のバージョンにより差分が出る可能性があるため、実装方針を理解するための擬似コード寄りのサンプルとして扱ってください。

v4 旧実装のイメージ

v4 では IStoreListener を実装し、初期化・商品取得・購入結果・購入失敗を同じクラスで受け取る構成が一般的でした。

using UnityEngine;
using UnityEngine.Purchasing;

public class InAppPurchaseManagerV4 : MonoBehaviour, IStoreListener
{
    private IStoreController storeController;
    private IExtensionProvider extensionProvider;

    private const string ProductIdGem480 = "dev.example.gem480";

    public void Initialize()
    {
        var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());

        builder.AddProduct(ProductIdGem480, ProductType.Consumable);

        UnityPurchasing.Initialize(this, builder);
    }

    public void BuyGem480()
    {
        if (storeController == null)
        {
            Debug.LogError("IAP is not initialized.");
            return;
        }

        storeController.InitiatePurchase(ProductIdGem480);
    }

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

        Debug.Log("IAP initialized.");
    }

    public void OnInitializeFailed(InitializationFailureReason error)
    {
        Debug.LogError($"IAP initialize failed: {error}");
    }

    public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs args)
    {
        var product = args.purchasedProduct;

        // v4 ではここでレシートを取り出し、サーバー検証へ投げる実装が多い
        var receipt = product.receipt;

        // サーバー検証が非同期の場合、Pending を返す
        SendReceiptToServer(
            product.definition.id,
            receipt,
            onSuccess: () =>
            {
                // サーバー側で付与成功後に ConfirmPendingPurchase
                storeController.ConfirmPendingPurchase(product);
            });

        return PurchaseProcessingResult.Pending;
    }

    public void OnPurchaseFailed(Product product, PurchaseFailureReason failureReason)
    {
        Debug.LogError($"Purchase failed: {product.definition.id}, reason: {failureReason}");
    }

    private void SendReceiptToServer(string productId, string receipt, System.Action onSuccess)
    {
        // TODO: 既存の check_receipt API へ送信
        // 成功したら onSuccess を呼ぶ
    }
}

v4 の特徴

  • UnityPurchasing.Initialize(...) が初期化の中心
  • IStoreListener にイベントが集約される
  • ConfigurationBuilder.AddProduct(...) で商品を登録
  • ProcessPurchase(...) の戻り値で Complete / Pending を制御
  • ConfirmPendingPurchase(...) で保留中の購入を完了する

v5 新実装のイメージ

v5 では、ストア接続、商品取得、購入、購入確認をより明示的に分けて実装します。

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

public class InAppPurchaseManagerV5 : MonoBehaviour
{
    private StoreController storeController;

    private const string ProductIdGem480 = "dev.example.gem480";

    public async void Initialize()
    {
        storeController = UnityIAPServices.StoreController();

        // 購入成功・失敗・保留中購入などのイベントを登録
        storeController.OnPurchasePending += OnPurchasePending;
        storeController.OnProductsFetched += OnProductsFetched;
        storeController.OnPurchasesFetched += OnPurchasesFetched;
        storeController.OnPurchaseConfirmed += OnPurchaseConfirmed;
        storeController.OnPurchaseFailed += OnPurchaseFailed;

        // 1. ストアへ接続
        await storeController.Connect();

        // 2. 商品情報を取得
        var productDefinitions = new List<ProductDefinition>
        {
            new ProductDefinition(ProductIdGem480, ProductType.Consumable),
        };

        storeController.FetchProducts(productDefinitions);
    }

    private void OnProductsFetched(List<Product> products)
    {
        // 3. 商品取得後、未完了・復元対象の購入を取得する
        storeController.FetchPurchases();

        Debug.Log("IAP v5 initialized.");
    }

    private void OnPurchasesFetched(Orders orders)
    {
        // restored purchase / pending purchase / completed order を確認する
        // 非消費型・サブスクリプションの権利確認もここで行う
    }

    public void BuyGem480()
    {
        if (storeController == null)
        {
            Debug.LogError("IAP is not initialized.");
            return;
        }

        storeController.PurchaseProduct(ProductIdGem480);
    }

    private async void OnPurchasePending(PendingOrder pendingOrder)
    {
        // v5 では、購入を受け取った後にサーバー検証を行い、
        // 成功してから Confirm する流れを明示的に実装する
        var productId = pendingOrder.Info.PurchasedProductInfo.productId;
        var receipt = pendingOrder.Info.Receipt;

        var result = await SendReceiptToServer(productId, receipt);

        if (result.IsSuccess)
        {
            // サーバー検証とアイテム付与が成功した後に Confirm
            storeController.ConfirmPurchase(pendingOrder);
        }
        else
        {
            // Confirm しなければ pending として残り、次回起動時などに再処理できる
            Debug.LogError($"Receipt validation failed: {productId}");
        }
    }

    private void OnPurchaseConfirmed(Order order)
    {
        Debug.Log($"Purchase confirmed: {order.Info.TransactionID}");
    }

    private void OnPurchaseFailed(FailedOrder failedOrder)
    {
        Debug.LogError($"Purchase failed: {failedOrder.Info}");
    }

    private async System.Threading.Tasks.Task<ReceiptValidationResult> SendReceiptToServer(
        string productId,
        string receipt)
    {
        // TODO: v5 で取得できる receipt / payload / transactionId などを
        // 既存の check_receipt API に合わせて送信する
        //
        // サーバー側では Apple / Google の検証 API に問い合わせ、
        // 成功した場合のみアイテム・通貨を付与する

        await System.Threading.Tasks.Task.CompletedTask;
        return new ReceiptValidationResult { IsSuccess = true };
    }

    private class ReceiptValidationResult
    {
        public bool IsSuccess;
    }
}

旧実装と新実装の比較

項目 v4 旧実装 v5 新実装
初期化 UnityPurchasing.Initialize(...) UnityIAPServices.StoreController() を取得し、Connect() を実行
商品登録 ConfigurationBuilder.AddProduct(...) ProductDefinition を作成して商品取得
商品取得 初期化時にまとめて取得されるイメージ FetchProducts(...) で明示的に取得
購入開始 storeController.InitiatePurchase(...) storeController.PurchaseProduct(...)
購入成功 ProcessPurchase(...) 新規購入は OnPurchasePending、復元/取得済み購入は OnPurchasesFetched
購入完了 PurchaseProcessingResult.Complete または ConfirmPendingPurchase(...) サーバー検証後に ConfirmPurchase(...)
保留中購入 PurchaseProcessingResult.Pending Confirm されていない購入として再処理
レシート検証 product.receipt と CrossPlatformValidator またはサーバー検証 Order.Info.Receipt や Apple の jwsRepresentation など、ストアごとの情報を使う

実装時のポイント

1. v5 では「購入成功 = 付与完了」ではない

OnPurchasePending を受け取った時点では、まだサーバー検証やアイテム付与が完了していません。

そのため、以下の順序を守る必要があります。

  1. 購入イベントを受け取る
  2. レシートをサーバーへ送信する
  3. サーバーで Apple / Google に検証する
  4. サーバーでアイテム・通貨を付与する
  5. クライアントへ成功を返す
  6. Unity IAP 側で Confirm する

2. Confirm は最後に行う

サーバー検証や付与処理が完了する前に Confirm すると、途中で失敗した場合に購入の再処理が難しくなります。

そのため、v5 の実装では **Confirm は「サーバー側の付与完了後」**に限定するのが安全です。

3. 既存 check_receipt API の入力を見直す

既存 API が v4 の product.receipt だけを前提にしている場合、v5 で取得する Order.Info.Receipt や Apple の jwsRepresentation などに合わせて、入力仕様を見直す必要があります。

最低限、以下をログ出力して、v4 と v5 の差分を確認するのが良いです。

Debug.Log($"ProductId: {productId}");
Debug.Log($"TransactionId: {transactionId}");
Debug.Log($"Receipt: {receipt}");
Debug.Log($"Store: {storeName}");
Debug.Log($"Payload: {payload}");

4. pending purchase の再処理を必ず確認する

アプリ終了、通信失敗、サーバーエラーなどにより、Confirm 前の購入が残る可能性があります。

v5 移行時は、通常購入だけでなく以下もテスト対象に含めるべきです。

  • 購入直後にアプリを落とす
  • サーバー検証 API を一時的に失敗させる
  • 通信エラー状態で購入する
  • 次回起動時に pending purchase が再処理されるか確認する

移行時のチェックリスト

  • Unity IAP パッケージを v5 系へ更新
  • 既存の v4 API 利用箇所を洗い出す
  • IStoreListener ベースの実装を v5 API へ置き換える
  • 商品定義・商品取得処理を v5 方式へ変更
  • 購入開始処理を v5 方式へ変更
  • 購入成功・失敗イベント処理を v5 方式へ変更
  • pending purchase の扱いを実装
  • サーバー検証完了後に Confirm する流れへ変更
  • CrossPlatformValidator 依存をなくす
  • Apple / Google それぞれのレシート検証値を確認
  • 既存 check_receipt API の入力仕様を v5 に合わせて確認・改修
  • Sandbox / TestFlight / Google Play Internal testing で購入・復元・再起動時 pending をテスト
  • サブスクリプションがある場合、更新・解約・期限切れ・復元の検証を追加

注意点

コンパイルエラーは正常な移行作業の一部

v4 から v5 へ更新すると、型定義や interface の廃止により既存コードがコンパイルエラーになる可能性が高いです。これは単純なメソッド名変更ではなく、IAP の構造自体が変わっているためです。

レシート検証はクライアント完結にしない

有償通貨や課金アイテムをサーバー側で管理している場合、クライアント側だけで検証・付与を完結させる構成は避けるべきです。

IAP v5 では、Google Play は Order.Info.Receipt、Apple は StoreKit 2 の jwsRepresentation など、ストアごとの検証情報を確認して設計する必要があります。

Confirm のタイミングに注意

購入を Confirm する前にアプリが終了した場合、購入は pending として再処理対象になります。

逆に、サーバー検証やアイテム付与が完了する前に Confirm してしまうと、失敗時のリカバリが難しくなります。

そのため、Confirm は必ず以下の後に行います。

  1. レシート検証成功
  2. サーバー側で付与成功
  3. クライアント側で成功レスポンス確認

まとめ

Unity IAP v5 への移行は、単なるパッケージ更新ではなく、IAP Manager の再設計に近いメジャーアップデートです。

特に重要なのは以下です。

  • v4 の UnityPurchasing.Initialize / IStoreListener 中心の構成から、v5 のサービス分離型 API へ移行する
  • 商品取得・購入・購入完了処理を明示的に管理する
  • 購入完了はサーバー検証と付与完了後に Confirm する
  • Product.receipt 前提の実装を見直し、Order.Info.Receipt や Apple の jwsRepresentation など v5 の購入情報に合わせる
  • Apple / Google で必要な検証情報の違いを確認する
  • 既存の check_receipt API が v5 の購入情報に対応できるか検証する

既存プロジェクトでは、まず現在の InAppPurchase Manager が v4 API のどこに依存しているかを洗い出し、初期化・商品取得・購入・検証・Confirm の順に段階的に置き換えるのが安全です。


引用

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?