How to manage a large catalog of in-app products (XAML)

[ This article is for Windows 8.x and Windows Phone 8.x developers writing Windows Runtime apps. If you’re developing for Windows 10, see the latest documentation ]

Windows 8.1 and Windows Phone 8.1 introduce a new solution for apps that offer in-app product catalogs that extend beyond the Store limitation of 200 product listings per developer account. This solution allows you to create just a handful of product entries for specific price tiers, with each one able to represent hundreds of products within a catalog.

To enable this capability a new overload of the RequestProductPurchaseAsync method was introduced to purchase an app-defined offer associated with an in-app product listed in the Store. In addition to specifying an offer and product association during the call, your app should also pass a ProductPurchaseDisplayProperties object that contains the large catalog offer details. If these details are not provided, the details for the listed product will be used instead.

The Store will only use the OfferId from the purchase request in the resulting PurchaseResults. This process does not directly modify the information originally provided when listing the in-app product in the Store.

What you need to know

Technologies

Prerequisites

  • This topic covers Store support for the representation of multiple in-app offers using a single in-app product listed in the Store. If you are unfamiliar with in-app purchases please review Enable in-app product purchases to learn about license information, and how to properly list your in-app purchase in the Store.

  • This topic also references code examples provided in the Trial app and in-app purchase sample available in the MSDN Code Gallery. This sample is a great way to get hands-on experience with the different monetization options provided for Windows Runtime apps.

  • When you code and test new in-app offers for the first time, you must use the CurrentAppSimulator object instead of the CurrentApp object. This way you can verify your license logic using simulated calls to the license server instead of calling the live server. To do this, you need to customize the file named "WindowsStoreProxy.xml" in %userprofile%\AppData\local\packages\<package name>\LocalState\Microsoft\Windows Store\ApiData. The Microsoft Visual Studio simulator creates this file when you run your app for the first time—or you can also load a custom one at runtime. For more info, see CurrentAppSimulator docs.

Make the purchase request for the in-app product

The purchase request for a specific product within a large catalog is handled in much the same way as any other purchase request within an app. When your app calls the new RequestProductPurchaseAsync method overload, your app provides both an OfferId, and a ProductPurchaseDisplayProperties object populated with the name of the in-app product.

string offerId = "1234";
string displayPropertiesName = "MusicOffer1";
var displayProperties = new ProductPurchaseDisplayProperties(displayPropertiesName);

try
{
    PurchaseResults purchaseResults = await CurrentAppSimulator.RequestProductPurchaseAsync("product1", offerId, displayProperties);
    switch (purchaseResults.Status)
    {
        case ProductPurchaseStatus.Succeeded:
            // Grant the user their purchase here, and then pass the product ID and transaction ID to currentAppSimulator.reportConsumableFulfillment
            // To indicate local fulfillment to the Windows Store.
            break;
        case ProductPurchaseStatus.NotFulfilled:
            // First check for unfulfilled purchases and grant any unfulfilled purchases from an earlier transaction.
            // Once products are fulfilled pass the product ID and transaction ID to currentAppSimulator.reportConsumableFulfillment
            // To indicate local fulfillment to the Windows Store.
            break;
        case ProductPurchaseStatus.NotPurchased:
            // Notify user that the purchase was not completed due to cancellation or error.
            break;
    }
}
catch (Exception)
{
    //Notify the user of the purchase error.
}

Report fulfillment of the in-app offer

Your app will need to report product fulfillment to the Store as soon as the offer has been fulfilled locally. In a large catalog scenario, if your app does not report offer fulfillment, the user will be unable to purchase any in-app offers using that same Store product listing.

As mentioned earlier, the Store only uses provided offer info to populate the PurchaseResults, and does not create a persistent association between a large catalog offer and Store product listing. As a result you need to track user entitlement for products, and provide product-specific context (such as the name of the item being purchased or details about it) to the user outside of the RequestProductPurchaseAsync operation.

The following code demonstrates the fulfillment call, and a UI messaging pattern in which the specific offer info is inserted. In the absence of that specific product info, the example uses info from the product ListingInformation.

string offerId = "1234";
product1ListingName = product1.Name;
string displayPropertiesName = "MusicOffer1";

if (String.IsNullOrEmpty(displayPropertiesName))
{
    displayPropertiesName = product1ListingName;
}
var offerIdMsg = " with offer id " + offerId;
if (String.IsNullOrEmpty(offerId))
{
    offerIdMsg = " with no offer id";
}

FulfillmentResult result = await CurrentAppSimulator.ReportConsumableFulfillmentAsync(productId, transactionId);
switch (result)
{
    case FulfillmentResult.Succeeded:
        Log("You bought and fulfilled " + displayPropertiesName + offerIdMsg, NotifyType.StatusMessage);
        break;
    case FulfillmentResult.NothingToFulfill:
        Log("There is no purchased product 1 to fulfill.", NotifyType.StatusMessage);
        break;
    case FulfillmentResult.PurchasePending:
        Log("You bought product 1. The purchase is pending so we cannot fulfill the product.", NotifyType.StatusMessage);
        break;
    case FulfillmentResult.PurchaseReverted:
        Log("You bought product 1. But your purchase has been reverted.", NotifyType.StatusMessage);
        // Since the user's purchase was revoked, they got their money back.
        // You may want to revoke the user's access to the consumable content that was granted.
        break;
    case FulfillmentResult.ServerError:
        Log("You bought product 1. There was an error when fulfilling.", NotifyType.StatusMessage);
        break;
}

Trial app and in-app purchase sample

Enable in-app product purchases

Enable consumable in-app product purchases

RequestProductPurchaseAsync

ProductPurchaseDisplayProperties