啟用消耗性附加元件購買

本文示範如何使用 Windows.Services.Store 命名空間中 StoreContext 類別的方法,來管理使用者在 UWP 應用程式中履行消耗性附加元件。 針對可購買、使用及再次購買的項目使用消耗性附加元件。 這對於可以購買的遊戲內貨幣 (金幣、硬幣等),之後用於特定道具等事項特別有用。

注意

Windows.Services.Store 命名空間是在 Windows 10 版本 1607 中引進的,而且在 Visual Studio 中只能用於以 Windows 10 年度版本 (10.0;組建 14393) 或更新版本為目標的專案。 如果您的應用程式以舊版 Windows 10 為目標,您必須使用 Windows.ApplicationModel.Store 命名空間,而不是 Windows.Services.Store 命名空間。 如需詳細資訊,請參閱這篇文章

消耗性附加元件概觀

應用程式可以提供兩種類型的消耗性附加元件,差別在於管理履行的方式不同:

  • 開發人員管理的消耗性產品。 針對這種類型的消耗性產品,您必須負責追蹤附加元件所代表項目的使用者餘額,並且在使用者取用所有項目之後,將附加元件購買回報給市集。 使用者必須等到您的應用程式回報先前的附加元件購買已履行,才能再次購買附加元件。

    例如,如果您的附加元件在遊戲中代表 100 枚硬幣,且使用者取用 10 枚硬幣,則您的應用程式或服務必須為使用者維持 90 枚硬幣的新剩餘餘額。 使用者取用全部 100 枚硬幣之後,您的應用程式必須將附加元件回報為已履行,然後使用者才能再次購買 100 枚硬幣附加元件。

  • 市集管理的消耗性產品。 針對這種類型的消耗性產品,市集會追蹤附加元件所代表項目的使用者餘額。 當使用者取用任何項目時,您必須負責向市集將這些項目回報為已履行,而市集會更新使用者的餘額。 使用者可以購買附加元件的次數不限 (他們不需要先取用項目)。 您的應用程式可以隨時查詢市集,以取得使用者的目前餘額。

    例如,如果您的附加元件代表遊戲中 100 枚硬幣的初始數量,且使用者取用 50 枚硬幣,則您的應用程式會向市集回報已履行 50 個單位的附加元件,而市集會更新剩餘餘額。 如果使用者接著重新購買您的附加元件以取得 100 枚硬幣,他們現在總共會有 150 枚硬幣。

    注意

    市集管理的消耗性產品是在 Windows 10 版本 1607 中引進的。

若要為使用者提供消耗性附加元件,請遵循下列一般程序:

  1. 讓使用者能夠從您的應用程式購買附加元件
  2. 當使用者取用附加元件時 (例如,他們在遊戲中花費硬幣),將附加元件回報為已履行

對於市集管理的消耗性產品,您也可以隨時取得剩餘的餘額

必要條件

這些範例具有下列必要條件:

  • Windows 10 年度版本 (10.0;組建 14393) 或更新版本為目標之通用 Windows 平台 (UWP) 應用程式的 Visual Studio 專案。
  • 您已在合作夥伴中心建立應用程式提交,且此應用程式會在市集中發佈。 您可以選擇將應用程式設定為在您進行測試時無法在市集中探索。 如需詳細資訊,請參閱我們測試指導方針
  • 您已在合作夥伴中心為應用程式建立消耗性附加元件

這些範例中的程式碼假設:

  • 程序碼會在 Page 的內容中執行,其中包含名為 workingProgressRingProgressRing 和名為 textBlockTextBlock。 這些物件是用來指出非同步作業正在發生,以及分別顯示輸出訊息。
  • 程式碼檔案具有 Windows.Services.Store 命名空間的 using 陳述式。
  • 應用程式是單一使用者應用程式僅會在啟動應用程式的使用者內容中執行。 如需詳細資訊,請參閱應用程式內購買和試用版

如需完整的範例應用程式,請參閱市集範例

注意

如果您有使用傳統型橋接器的傳統型應用程式,可能需要新增未顯示在這些範例中的其他程式碼來設定 StoreContext 物件。 如需詳細資訊,請參閱在使用傳統型橋接器的傳統型應用程式中使用 StoreContext 類別

將消耗性附加元件回報為已履行

當使用者從您的應用程式購買附加元件並取用附加元件之後,您的應用程式必須呼叫 StoreContext 類別的 ReportConsumableFulfillmentAsync 方法,將附加元件回報為已履行。 您必須將下列資訊傳遞至此方法:

  • 您想要回報為已履行之附加元件的 Store ID
  • 您想要回報為已履行的附加元件單位。
    • 針對開發人員管理的消耗性產品,請為 quantity 參數指定 1。 這會向市集通知消耗性產品已履行,然後客戶就可以再次購買消耗性產品。 使用者必須等到您的應用程式向市集通知已履行,才能再次購買消耗性產品。
    • 針對市集管理的消耗性產品,指定已取用的實際單位數目。 市集會更新消耗性產品的剩餘餘額。
  • 履行的追蹤識別碼。 這是開發人員提供的 GUID,可識別基於追蹤目的與履行作業鄉關聯的特定交易。 如需詳細資訊,請參閱 ReportConsumableFulfillmentAsync 中的備註。

此範例示範如何將市集管理的消耗性產品回報為已履行。

private StoreContext context = null;

public async void ConsumeAddOn(string addOnStoreId)
{
    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;
    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 addOnStoreId)
{
    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.
    }

    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;
    }
}