コンシューマブルなアドオン購入の有効化

Windows 10 バージョン 1607 以降をターゲットとするアプリは、Windows.Services.Store 名前空間で StoreContext クラスのメソッドを使って、ユーザーによる UWP アプリでのコンシューマブルなアドオンフルフィルメントを管理できます。 コンシューマブルなアドオンは、購入、使用、再購入可能なアイテムに使います。 これは、購入して、特定のパワーアップを購入するために使うことができるゲーム内通貨 (ゴールド、コインなど) 用に特に便利です。

注意

この記事は、Windows 10 バージョン 1607 以降をターゲットとするアプリに適用されます。 アプリが Windows 10 の以前のバージョンをターゲットとする場合、Windows.Services.Store 名前空間の代わりに Windows.ApplicationModel.Store 名前空間を使う必要があります。 詳しくは、この記事をご覧ください。

コンシューマブルなアドオンの概要

Windows 10 バージョン 1607 以降をターゲットとするアプリは、フルフィルメントを管理する方法が異なる 2 種類のコンシューマブルなアドオンを提供することができます。

  • 開発者により管理されるコンシューマブル。 この種類のコンシューマブルでは、アドオンが表すユーザーのアイテムの残量を追跡し、ユーザーによってすべてのアイテムが消費されたらアドオンの購入をフルフィルメント完了としてストアに報告する義務が開発者にあります。 以前のアドオン購入をフルフィルメント完了としてアプリで報告するまで、ユーザーがそのアドオンを再購入することはできません。

    たとえば、アドオンがゲーム内の 100 コインを表しており、ユーザーによって 10 コインが消費されている場合、アプリまたはサービスでは、ユーザーの 90 コインの残高を保持する必要があります。 ユーザーによって 100 コインすべてが消費されたら、アプリでそのアドオンをフルフィルメント完了として報告する必要があります。その後、ユーザーは 100 コイン アドオンを再購入できます。

  • ストアで管理されるコンシューマブル。 この種類のコンシューマブルでは、アドオンが表すユーザーのアイテムの残量はストアで追跡します。 ユーザーによりアイテムが消費されたら、そのアイテムをフルフィルメント完了としてストアに報告する義務が開発者にあり、ストアによってユーザーの残量が更新されます。 アプリでは、ユーザーのアイテムの現在の残量をいつでも照会できます。 ユーザーは、すべてのアイテムを消費したら、そのアドオンを再購入できます。

    たとえば、アドオンがゲーム内の 100 のコインの初期量を表しており、ユーザーによって 10 コインが消費された場合、そのアドオンの 10 ユニットがフルフィルメント完了したことをアプリでストアに報告すると、ストアにより残高が更新されます。 ユーザーは、100 コインすべてを消費した後、その 100 コインのアドオンを再購入できます。

コンシューマブルなアドオンをユーザーに提供するには、この一般的なプロセスに従います。

  1. ユーザーがアプリからアドオンを購入するようにしてください。
  2. ユーザーがアドオンを消費するとき (たとえば、ゲームでコインを消費する場合)、アドオンをフルフィルメント完了として報告します。

さらに、ストアで管理されるコンシューマブルの場合、いつでも残高を取得することができます。

前提条件

これらの例には、次の前提条件があります。

  • Windows 10 バージョン 1607 以降をターゲットとするユニバーサル Windows プラットフォーム (UWP) アプリの Visual Studio プロジェクト。
  • Windows デベロッパー センター ダッシュボードでアプリの申請を作成し、このアプリがストアで公開されている。 必要に応じで、テスト中にストアでアプリを検索できないようにアプリを構成することも可能です。 詳しくは、テスト ガイダンスをご覧ください。
  • デベロッパー センター ダッシュボードでアプリのコンシューマブルなアドオンを作成した。

これらの例のコードは、次の点を前提としています。

  • コードは、workingProgressRing という名前の ProgressRingtextBlock という名前の TextBlock を含む Page のコンテキストで実行されます。 これらのオブジェクトは、それぞれ非同期操作が発生していることを示するためと、出力メッセージを表示するために使用されます。
  • コード ファイルには、Windows.Services.Store 名前空間の using ステートメントがあります。
  • アプリは、アプリを起動したユーザーのコンテキストでのみ動作するシングル ユーザー アプリです。 詳しくは、「アプリ内購入と試用版」をご覧ください。

完全なサンプル アプリケーションについては、ストア サンプルをご覧ください。

注意

デスクトップ ブリッジを使用するデスクトップ アプリケーションがある場合、これらの例には示されていないコードを追加して StoreContext オブジェクトを構成することが必要になることがあります。 詳しくは、「デスクトップ ブリッジを使用するデスクトップ アプリケーションでの StoreContext クラスの使用」をご覧ください。

コンシューマブルなアドオンをフルフィルメント完了として報告する

ユーザーがアプリからアドオンを購入してアドオンを消費したら、StoreContext クラスの ReportConsumableFulfillmentAsync メソッドを呼び出すことでアドオンをフルフィルメント完了として報告する必要があります。 次の情報をこのメソッドに渡す必要があります。

  • フルフィルメント完了として報告するアドオンのストア ID
  • フルフィルメント完了として報告するアドオンの単位。
    • 開発者により管理されるコンシューマブルの場合、quantity パラメーターには 1 を指定します。 これにより、コンシューマブルのフルフィルメントが完了したことがストアに通知され、ユーザーがそのコンシューマブルをもう一度購入できるようになります。 ユーザーは、アプリがストアにフルフィルメント完了を通知するまで、コンシューマブルをもう一度購入することができません。
    • ストアで管理されるコンシューマブルの場合、消費された単位の実際の数を指定します。 ストアにより、コンシューマブルの残高が更新されます。
  • フルフィルメントの追跡 ID。 これは、開発者が指定する GUID で、追跡目的でフルフィルメント操作が関連付けられている特定のトランザクションを識別します。 詳しくは、「ReportConsumableFulfillmentAsync」の解説をご覧ください。

この例では、ストアで管理されるコンシューマブルをフルフィルメント完了として報告する方法を示します。

private StoreContext context = null;

public async void ConsumeAddOn(string storeId)
{
    if (context == null)
    {
        context = StoreContext.GetDefault();
        // If your app is a desktop app that uses the Desktop Bridge, you
        // may need additional code to configure the StoreContext object.
        // For more info, see https://aka.ms/storecontext-for-desktop.
    }

    // This is an example for a Store-managed consumable, where you specify the actual number
    // of units that you want to report as consumed so the Store can update the remaining
    // balance. For a developer-managed consumable where you maintain the balance, specify 1
    // to just report the add-on as fulfilled to the Store.
    uint quantity = 10;
    string addOnStoreId = "9NBLGGH4TNNR";
    Guid trackingId = Guid.NewGuid();

    workingProgressRing.IsActive = true;
    StoreConsumableResult result = await context.ReportConsumableFulfillmentAsync(
        addOnStoreId, quantity, trackingId);
    workingProgressRing.IsActive = false;

    // Capture the error message for the operation, if any.
    string extendedError = string.Empty;
    if (result.ExtendedError != null)
    {
        extendedError = result.ExtendedError.Message;
    }

    switch (result.Status)
    {
        case StoreConsumableStatus.Succeeded:
            textBlock.Text = "The fulfillment was successful. " + 
                $"Remaining balance: {result.BalanceRemaining}";
            break;

        case StoreConsumableStatus.InsufficentQuantity:
            textBlock.Text = "The fulfillment was unsuccessful because the remaining " +
                $"balance is insufficient. Remaining balance: {result.BalanceRemaining}";
            break;

        case StoreConsumableStatus.NetworkError:
            textBlock.Text = "The fulfillment was unsuccessful due to a network error. " +
                "ExtendedError: " + extendedError;
            break;

        case StoreConsumableStatus.ServerError:
            textBlock.Text = "The fulfillment was unsuccessful due to a server error. " +
                "ExtendedError: " + extendedError;
            break;

        default:
            textBlock.Text = "The fulfillment was unsuccessful due to an unknown error. " +
                "ExtendedError: " + extendedError;
            break;
    }
}

ストアで管理されるコンシューマブルの残高の取得

この例では、StoreContext クラスの GetConsumableBalanceRemainingAsync メソッドを使って、ストアで管理されるコンシューマブルなアドオンを取得する方法を示します。

private StoreContext context = null;

public async void GetRemainingBalance(string storeId)
{
    if (context == null)
    {
        context = StoreContext.GetDefault();
        // If your app is a desktop app that uses the Desktop Bridge, you
        // may need additional code to configure the StoreContext object.
        // For more info, see https://aka.ms/storecontext-for-desktop.
    }

    string addOnStoreId = "9NBLGGH4TNNR";

    workingProgressRing.IsActive = true;
    StoreConsumableResult result = await context.GetConsumableBalanceRemainingAsync(addOnStoreId);
    workingProgressRing.IsActive = false;

    // Capture the error message for the operation, if any.
    string extendedError = string.Empty;
    if (result.ExtendedError != null)
    {
        extendedError = result.ExtendedError.Message;
    }

    switch (result.Status)
    {
        case StoreConsumableStatus.Succeeded:
            textBlock.Text = "Remaining balance: " + result.BalanceRemaining;
            break;

        case StoreConsumableStatus.NetworkError:
            textBlock.Text = "Could not retrieve balance due to a network error. " +
                "ExtendedError: " + extendedError;
            break;

        case StoreConsumableStatus.ServerError:
            textBlock.Text = "Could not retrieve balance due to a server error. " +
                "ExtendedError: " + extendedError;
            break;

        default:
            textBlock.Text = "Could not retrieve balance due to an unknown error. " +
                "ExtendedError: " + extendedError;
            break;
    }
}