商店的基本操作

游戏内的商店通常会涉及 3 个基本操作:

  1. 确定可购买的产品
  2. 评估所拥有的产品
  3. 购买符合条件的产品

本文档将阐释与每个操作相关的示例代码。 这源自 InGameStore 示例,该示例会不断更新以反映最佳做法。

准备调用 XStore API

所有 XStore API 均通过使用 XStoreCreateContext 创建的 XStoreContextHandle 进行运行。

通过此上下文,可在主机上的指定用户和电脑上的默认可用用户上下文中执行商店操作。 在主机上,挂起或快速恢复事件后上下文无效。 为了安全地处理这些情况,建议关闭 XStoreContextHandle 并在游戏从暂停状态恢复时重新创建它。

1. 确定可购买的产品

游戏通常会提供购买的是附加内容。 以下代码演示了用于让游戏了解哪些产品可用的基本 XStoreQueryAssociatedProductsAsync API 调用。

此查询将自动返回与游戏相关联的可购买附加内容。 只要游戏在合作伙伴中心的产品关系设置部分中设置了与相应产品的“可销售”关系,则也可以在此调用中返回与同一发行商关联的不相关产品(即用同一合作伙伴中心帐户配置的产品)。

注意

请务必使用 XStoreProductsQueryHasMorePagesXStoreProductsQueryNextPageAsync 处理分页,即使预期产品的数量小于初始查询中指定的页面大小时也是如此。 这是因为应用商店服务保留将结果拆分到小于指定页面大小的多个页面的权限。

struct QueryContext
{
    Sample* pThis;
    uint32_t count;                      // used to accumulate count of products returned across pages
    XStoreProductQueryHandle handle;     // handle to reuse for paged operations
};

bool CALLBACK ProductEnumerationCallback(const XStoreProduct* product, void* context)
{
    // Handle adding the product to the game

    printf("%s %s %u\n", product->title, product->storeId, product->productKind);
    count++;

    return true;
}

void CALLBACK QueryAssociatedProductsCallback(XAsyncBlock* async)
{
    auto&[pThis, count, queryHandle] = *reinterpret_cast<QueryContext*>(async->context);

    HRESULT hr = XStoreQueryAssociatedProductsResult(async, &queryHandle);
    if (SUCCEEDED(hr))
    {
        hr = XStoreEnumerateProductsQuery(queryHandle, async->context, ProductEnumerationCallback);

        if (SUCCEEDED(hr))
        {
            if (XStoreProductsQueryHasMorePages(queryHandle))
            {
                printf("Has more pages!\n");
                pThis->QueryNextPage(async);
            }
            else
            {
                printf("Enumeration complete, %u products found\n", count);
                delete async;
            }
        }
        else
        {
            delete async;
        }
    }
}

void Sample::QueryCatalog()
{
    auto async = new XAsyncBlock{};
    async->context = new QueryContext{ this, 0, nullptr };
    async->queue = m_asyncQueue;
    async->callback = QueryAssociatedProductsCallback;

    XStoreProductKind typeFilter =
        XStoreProductKind::Consumable |
        XStoreProductKind::Durable |
        XStoreProductKind::Game;

    HRESULT hr = XStoreQueryAssociatedProductsAsync(
        m_xStoreContext,
        typeFilter,
        25,             // Products per page (25 is good default)
        async);

    if (FAILED(hr))
    {
        delete async;
    }
}

void Sample::QueryNextPage(XAsyncBlock *async)
{
    async->callback = [](XAsyncBlock* async)
    {
        if (SUCCEEDED(XStoreProductsQueryNextPageResult(async, &queryHandle)))
        {
            auto&[pThis, count, queryHandle] = *reinterpret_cast<QueryContext*>(async->context);

            HRESULT hr = XStoreEnumerateProductsQuery(queryHandle, async->context, ProductEnumerationCallback);

            if (SUCCEEDED(hr))
            {
                if (XStoreProductsQueryHasMorePages(queryHandle))
                {
                    printf("Has more pages!\n");
                    pThis->QueryNextPage(async);
                }
                else
                {
                    printf("Enumeration complete, %u products found\n", count);
                    delete async;
                }
            }
            else
            {
                delete async;
            }

        }
    };

    HRESULT result = XStoreProductsQueryNextPageAsync(queryHandle, async);
    if (FAILED(result))
    {
        delete async;
    }
}

注意事项

  • 只有可购买的产品才会返回 XStoreQueryAssociatedProductsAsync;只通过捆绑销售的产品或其他不能独立购买的产品将不会被返回。 对于这些用法,请参阅下面描述的 XStoreQueryProductsAsync
  • 确保查询句柄通过分页递归向下传递,以免泄漏。
  • 不会提前知晓将返回的产品数量,因此必须累计计数。

其他选项

如果知道商店 ID 或需要其他 actionFilters,则 XStoreQueryProductsAsync 可用于查询特定产品。 “操作”是适用于产品的使用场景,其中包含购买颁发许可证兑换之类的动词。

XStoreQueryProductForCurrentGameAsync 仅用于查询当前正在运行的游戏的产品。

XStoreShowAssociatedProductsUIAsync 将使用户转换到 Microsoft Store 应用,以查看关联产品的视图,该视图可以按产品种类进行筛选。 这是枚举和呈现游戏中的可用产品的替代方法。

注意

请注意,此 API 在 Web 浏览器中打开应用商店,如果游戏显示在主机、电脑上的 Xbox 应用或游戏流式处理店面中,建议使用 XStoreShowProductPageUI 或 XStoreShowPurchaseUI。

此游戏的加载项

2. 评估所拥有的产品

此操作涉及与上述相同的代码,包括管理页面的需要,并替换了以下内容:

借助这些 API,结果将包含调用用户帐户特别授权的产品。 “授权”可以表示直接拥有,也可以表示通过拥有父级捆绑或订阅进行实现。

请注意,这也可以由 XStoreQueryAssociatedProductsAsync 的结果确定,因为 XStoreProduct 包含 isInUserCollection 字段,授权时已将该字段设置为 true。

易耗品所有权

XStoreProduct.skus[i].collectionData.quantity 中注明了易耗品数量。 通常,易耗的产品仅有一个 SKU。

也可以使用 XStoreQueryConsumableBalanceRemainingAsync 单独查询数量,但建议不要分别查询大量易耗品,因为每次查询都会引起服务调用。

耐用品所有权

仅检查帐户是否拥有该产品,不足以确定是否应授权其在游戏内使用该产品。 耐用品必须遵守游戏的产品共享模型中所述的内容共享策略。

XStoreAcquireLicenseForPackageAsync 用于有包装的耐用品,以根据内容共享的条款确定是否可向其发放许可证。

XStoreAcquireLicenseForDurablesAsync 用于没有包装的耐用品以执行相同操作。

XStoreQueryAddOnLicensesAsync 可用于数字许可的游戏,以返回可许可耐用品(没有包装)列表。

有关详细信息,请参阅管理和许可可下载内容如何使用没有包装的耐用品

3. 购买符合条件的产品

只需将 Store ID 传递到 XStoreShowPurchaseUIAsync API 中,即可显示可购买产品的购买流:

void Sample::MakePurchase(const char* storeId)
{
    struct PurchaseContext
    {
        Sample* pThis;
        std::string storeId;
    };

    auto async = new XAsyncBlock{};
    async->context = new PurchaseContext{ this, storeId };
    async->queue = m_asyncQueue;
    async->callback = [](XAsyncBlock *async)
    {
        auto &[pThis, storeId] = *reinterpret_cast<PurchaseContext*>(async->context);

        HRESULT hr = XStoreShowPurchaseUIResult(async);
        if (SUCCEEDED(hr))
        {
            printf("Purchase succeeded (%s)\n", storeId.c_str());

            // Refresh ownership and update game
        }
        else
        {
            printf("Purchase failed (%s) 0x%x\n", storeId.c_str(), hr);

            if (hr == E_GAMESTORE_ALREADY_PURCHASED)
            {
                printf("Already own this\n");
            }
        }

        delete async;
    };

    HRESULT hr = XStoreShowPurchaseUIAsync(
        m_xStoreContext,
        storeId,
        nullptr,    // Can be used to override the title bar text
        nullptr,    // Can be used to provide extra details to purchase
        async);

    if (FAILED(hr))
    {
        delete async;
        printf("Error calling XStoreShowPurchaseUIAsync : 0x%x\n", hr);
        return;
    }
}

除了在异步回调中处理潜在购买的结果外,还可以通过明确切换到 Microsoft Store,在游戏之外进行购买。 还可在 Xbox.com、电脑、移动应用或其他专营店进行购买。 建议在游戏中有一个流,可以根据需要可靠地刷新产品所有权,例如转换到游戏内商店或设置中的某个位置,而不仅仅是作为初始登录流的一部分。

另请参阅

商业概述

启用 XStore 开发和测试

XStore API 参考