What’s New in Windows Phone 8 (7 out of 8)–In-App-Purchases

November 7, 2012

This post talks about new monetization feature in Windows Phone 8 – In-App-Purchases (IAP). According to multiple industry sources (link1, link2, link3) IAP can actually make more money that free/ad-supported application. Let’s see how to add this feature to application. In this post I will show how to build simplest “virtual” shop app. All IAP functionality process allocated in Windows.ApplicationModel.Store namespace.

First let’s understand the terminology associated with IAP process.

Product – something you are selling in application. Unlike Windows 8, in Windows Phone 8 we got two different product types: Durable and Consumable.

Durable – a Product that is purchased once and owned by the user/application.

Consumable – a Product that is purchased and used multiple times. This Product could be purchased again and again when user needs it.

Products are organized into Product List which managed through application’s settings on Dev Center Dashboard.

image

image

image

Purchase – the process of buying a Product.

License – a set of definitions describing the product type, whether it is active and its expiration date. Associated with each Product.

Receipt – a proof of purchase which used to verify that user Purchased the Product.

Fulfillment – the process of giving the user the Product he/she Purchased. Depends on application logic, but must me reported to marketplace. Available only for Consumables and plays key role in Product Purchase process – new Consumable cannot be Purchased again without reporting previous successful fulfillment.

 

Working with Catalog

Current application licensing info and associated product list is obtained using CurrentApp class. This app provides key functionality of searching catalog (by keyword or productId), purchasing the products and report fulfillments. The following code snippet searches all products by keywords:

string[] keywords = new string[] { "tuxedo", "luxary", "wearing" };

var items = await Store.CurrentApp.LoadListingInformationByKeywordsAsync(keywords);

Next code snippet shows how to search specific products from product list:

string[] productIds = new string[] { "Durables.Tuxedo", "Durables.LuxaryWatch", "Consumables.CherryPie" };

var products = await Store.CurrentApp.LoadListingInformationByProductIdsAsync(productIds);

In addition to search functionality, CurrentApp class also provides access to full product list which is accessible through CurrentApp.LicenseInformation.ProductLicenses class. Each item in ProductLicenses is an object from ProductLicense class. This class enables querying product state by providing ExpirationDate, IsActive and IsConsumable properties. The following code snippet loads not purchased durables from product catalog:

private async void LoadDurables()

{

    List<string> productIDs = new List<string>();

 

    //Search all not purchased durables from catalog

    var res = from item in Store.CurrentApp.LicenseInformation.ProductLicenses

              where !item.Value.IsConsumable && !item.Value.IsActive

              select item;

 

    //Build productId list

    foreach (var item in res)

        productIDs.Add(item.Value.ProductId);

 

    //Bring not purchased products info (names, proces, etc.) from catalog and assign it to istbox

    var durables = await Store.CurrentApp.LoadListingInformationByProductIdsAsync(productIDs.ToArray());

    lstDurables.DataContext = durables.ProductListings;

}

In addition, this code snippet loads all product listings (info about the products) and shows them as list of products:

image

Loading consumables (also shown on screenshot) is similar:

private async void LoadConsumables()

{

    List<string> productIDs = new List<string>();

 

    var res = from item in Store.CurrentApp.LicenseInformation.ProductLicenses

              where item.Value.IsConsumable

              select item;

 

    foreach (var item in res)

        productIDs.Add(item.Value.ProductId);

 

    var consumables = await Store.CurrentApp.LoadListingInformationByProductIdsAsync(productIDs.ToArray());

 

    lstConsumables.DataContext = consumables.ProductListings;

}

Let’s see how to present the product on screen. On sample app screen (see above), when user changes selection we will be navigating to different screen with selected productId as navigation parameter:

NavigationService.Navigate(new Uri("/ProductInfo.xaml?PID=" + selectedItem.ProductId, UriKind.Relative));

Let’s see the sample ProductInfo page. I will be presenting product details on page as follows:

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">

    <Grid.RowDefinitions>

        <RowDefinition Height="Auto"/>

        <RowDefinition Height="*"/>

        <RowDefinition Height="Auto"/>

        <RowDefinition Height="Auto"/>

        <RowDefinition Height="Auto"/>

    </Grid.RowDefinitions>

    <Grid.ColumnDefinitions>

        <ColumnDefinition/>

        <ColumnDefinition/>

    </Grid.ColumnDefinitions>

    <TextBlock Text="{Binding Name}" Grid.ColumnSpan="2" Style="{StaticResource PhoneTextTitle2Style}" />

    <TextBlock Text="{Binding Description}" TextWrapping="Wrap" Grid.Column="0" Grid.Row="1" Style="{StaticResource PhoneTextSmallStyle}" />

    <StackPanel Grid.Column="0" Grid.Row="2" Orientation="Vertical" Margin="0, 10">

        <TextBlock Text="Product type:" Style="{StaticResource PhoneTextSmallStyle}" />

        <TextBlock Text="{Binding ProductType}" Style="{StaticResource PhoneTextSmallStyle}" />

    </StackPanel>

    <StackPanel Grid.Column="0" Grid.Row="3" Orientation="Vertical">

        <TextBlock Text="Purchase price:" Style="{StaticResource PhoneTextSmallStyle}" />

        <TextBlock Text="{Binding FormattedPrice}" Style="{StaticResource PhoneTextLargeStyle}"  FontWeight="Bold"/>

    </StackPanel>

    <Image Source="{Binding ImageUri, Converter={StaticResource StoreToImageUriConverter}}" Grid.Column="1" Grid.Row="1" Grid.RowSpan="3" VerticalAlignment="Top"/>

    <Button Grid.Row="4" Grid.Column="0" Content="Buy now" x:Name="btnPurchase" Click="btnPurchase_Click" HorizontalAlignment="Stretch" VerticalAlignment="Bottom"/>

    <Button Grid.Row="4" Grid.Column="1" Content="Cancel" x:Name="btnCancel" Click="btnCancel_Click" HorizontalAlignment="Stretch" VerticalAlignment="Bottom"/>

</Grid>

When application navigates to the page it searches product info from product list as follows:

protected override async void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)

{

    string productId = NavigationContext.QueryString["PID"];

 

    var pi = await Store.CurrentApp.LoadListingInformationByProductIdsAsync(new string[] { productId });

    productInfo = pi.ProductListings.First().Value;

 

    this.DataContext = productInfo;

    base.OnNavigatedTo(e);

}

This code produces the following results:

image

image

Now the interesting part – “Buy now button handler which does actual product purchase:

private async void btnPurchase_Click(object sender, RoutedEventArgs e)

{

    var receipt = await Store.CurrentApp.RequestProductPurchaseAsync(productInfo.ProductId, true);

 

    //Parse receipt

    //...

 

    if (productInfo.ProductType == Windows.ApplicationModel.Store.ProductType.Durable)

    {

        if (Store.CurrentApp.LicenseInformation.ProductLicenses[productInfo.ProductId].IsActive)

        {

            //Fulfill purchased durable -- app logic

            //...

        }

    }

    else if (productInfo.ProductType == Windows.ApplicationModel.Store.ProductType.Consumable)

    {

        if (Store.CurrentApp.LicenseInformation.ProductLicenses[productInfo.ProductId].IsActive)

        {

            //Fulfill purchased consumable -- app logic

            //...

 

            //Report fullfillment

            Store.CurrentApp.ReportProductFulfillment(productInfo.ProductId);

        }

    }

 

    NavigationService.GoBack();

}

Previously purchased (durable) products could be retrieved from listing as follows:

List<string> productIDs = new List<string>();

 

var res = from item in Store.CurrentApp.LicenseInformation.ProductLicenses

          where item.Value.IsActive

          select item;

 

foreach (var item in res)

    productIDs.Add(item.Value.ProductId);

 

var durables = await Store.CurrentApp.LoadListingInformationByProductIdsAsync(productIDs.ToArray());

lstPurchases.DataContext = durables.ProductListings;

 

Developing/Testing IAP scenarios

Working IAP is a server-based commerce. It relies on communication with Microsoft’s backend services to list products, enumerate licenses and process purchases. It may also rely on your servers to deliver purchased in-app products if needed. When purchasing the product (published application) marketplace will perform real transaction with client’s account. In development cycle it is not always possible to deploy application to marketplace (especially as public application).  It is possible to deploy application as Beta on marketplace. Beta applications support full IAP mechanism through marketplace with only one difference – although the user experience shows prices as entered into Dev Center, all in-app purchases made from beta apps are free. This approach is strongly recommended once you application is in somewhat ready state. But what to do during initial development stages? Windows 8 provides CurrentAppSimulator class for this work. Unfortunately this functionality didn’t made it into Windows Phone 8. To overcome this, we want to use mock library which closely mimics Store APIs.

This library exists and could be downloaded here.

To work with this library it must be referenced on all pages which are using Store functionality as follows:

#if DEBUG

    using MockIAPLib;

    using Store = MockIAPLib;

#else

    using Store = Windows.ApplicationModel.Store;

#endif

From now on, to test real marketplace functionality all you need is to compile your app as Release. While the library closely mimics store APIs it must be initiated with as it doesn’t uses server-based information from marketplace. It should be initiated from XML-based product catalog as follows:

<?xml version="1.0" encoding="utf-8" ?>

<ProductListings>

  <!-- Durables-->

  <ProductListing Key="Tuxedo" Purchased="false" Fulfilled="false">

    <Name>Tuxedo</Name>

    <Description>Classic tuxedo</Description>

    <ProductId>Durables.Tuxedo</ProductId>

    <ProductType>Durable</ProductType>

    <FormattedPrice>$299.99</FormattedPrice>

    <ImageUri>ms-appdata:///Images/Durables/Tuxedo.png</ImageUri>

    <Keywords>wearing;tuxedo</Keywords>

    <Tag>Pre-order only!</Tag>

  </ProductListing>

  <ProductListing Key="ButterflyCollar" Purchased="false" Fulfilled="false">

    <Name>Butterfly Collar</Name>

    <Description>Classic Butterfly Collar</Description>

    <ProductId>Durables.ButterflyCollar</ProductId>

    <ProductType>Durable</ProductType>

    <FormattedPrice>$19.99</FormattedPrice>

    <ImageUri>ms-appdata:///Images/Durables/ButterflyCollar.jpg</ImageUri>

    <Keywords>wearing;butterfly;collar</Keywords>

    <Tag></Tag>

  </ProductListing>

  <ProductListing Key="LuxaryWatch" Purchased="false" Fulfilled="false">

    <Name>Luxary Watch</Name>

    <Description>Luxary Watch</Description>

    <ProductId>Durables.LuxaryWatch</ProductId>

    <ProductType>Durable</ProductType>

    <FormattedPrice>$19999.99</FormattedPrice>

    <ImageUri>ms-appdata:///Images/Durables/LuxaryWatch.jpg</ImageUri>

    <Keywords>watch;luxary</Keywords>

    <Tag>Special order, call for availability!</Tag>

  </ProductListing>

 

  <!--Consumables-->

  <ProductListing Key="LemonPie" Purchased="false" Fulfilled="false">

    <Name>Lemon Pie</Name>

    <Description>Delicious Lemon Pie</Description>

    <ProductId>Consumables.LemonPie</ProductId>

    <ProductType>Consumable</ProductType>

    <FormattedPrice>$2.99</FormattedPrice>

    <ImageUri>ms-appdata:///Images/Consumables/LemonPie.jpg</ImageUri>

    <Keywords>food;desert;lemon;pie</Keywords>

    <Tag>Calories: 405 | Total Fat: 21.5g | Cholesterol: 47mg</Tag>

  </ProductListing>

  <ProductListing Key="CherryPie" Purchased="false" Fulfilled="false">

    <Name>Cherry Pie</Name>

    <Description>Delicious Cherry Pie</Description>

    <ProductId>Consumables.CherryPie</ProductId>

    <ProductType>Consumable</ProductType>

    <FormattedPrice>$3.49</FormattedPrice>

    <ImageUri>ms-appdata:///Images/Consumables/CherryPie.jpg</ImageUri>

    <Keywords>food;desert;cherry;pie</Keywords>

    <Tag>Calories: 506 | Total Fat: 27.5g | Cholesterol: 4mg</Tag>

  </ProductListing>

  <ProductListing Key="NYSteak" Purchased="false" Fulfilled="false">

    <Name>New York Steak</Name>

    <Description>Superb New York Steak</Description>

    <ProductId>Consumables.NYSteak</ProductId>

    <ProductType>Consumable</ProductType>

    <FormattedPrice>$12.99</FormattedPrice>

    <ImageUri>ms-appdata:///Images/Consumables/NewYorkSteak.jpg</ImageUri>

    <Keywords>food;meat;new york;steak</Keywords>

    <Tag>Calories: 624 | Total Fat: 28g | Cholesterol: 106mg</Tag>

  </ProductListing>

  <ProductListing Key="CaesarSalad" Purchased="false" Fulfilled="false">

    <Name>Caesar Salad</Name>

    <Description>Caesar Salad Supreme</Description>

    <ProductId>Consumables.CaesarSalad</ProductId>

    <ProductType>Consumable</ProductType>

    <FormattedPrice>$5.99</FormattedPrice>

    <ImageUri>ms-appdata:///Images/Consumables/CaesarSalad.jpg</ImageUri>

    <Keywords>food;salad;Caesar;starters</Keywords>

    <Tag>Calories: 384 | Total Fat: 33.5g | Cholesterol: 18mg</Tag>

  </ProductListing>

  <ProductListing Key="RedWine" Purchased="false" Fulfilled="false">

    <Name>Cabernet Sauvignon</Name>

    <Description>Glass of Cabernet Sauvignon Red Wine</Description>

    <ProductId>Consumables.CabernetSauvignon</ProductId>

    <ProductType>Consumable</ProductType>

    <FormattedPrice>$9.99</FormattedPrice>

    <ImageUri>ms-appdata:///Images/Consumables/RedWine.jpg</ImageUri>

    <Keywords>wine;drink;cabernet sauvignon</Keywords>

    <Tag></Tag>

  </ProductListing>

  <ProductListing Key="MadameClicquot" Purchased="false" Fulfilled="false">

    <Name>Madame Clicquot Champange</Name>

    <Description>Glass of Madame Clicquot Champange</Description>

    <ProductId>Consumables.MadameClicquot</ProductId>

    <ProductType>Consumable</ProductType>

    <FormattedPrice>$24.99</FormattedPrice>

    <ImageUri>ms-appdata:///Images/Consumables/MadameClicquot.jpg</ImageUri>

    <Keywords>wine;drink;champange;madame clicquot;luxary</Keywords>

    <Tag></Tag>

  </ProductListing>

</ProductListings>

Note: in real scenarios it is recommended to get product images from app’s server-side storage as the approach shown in this sample relays on having all possible product images in application package which is not recommended and prevents from extending product list after publishing the app.

Initialization code (in my case it is in App.xaml.cs:

#if DEBUG

            //Use Mock API instead of real store

            MockIAP.Init();

            MockIAP.RunInMockMode(true);

 

#if DONT_PRESERVE_LICENSE_INFO

            // Clear the cache every time. Remove this to have persistence.

            MockIAP.ClearCache();

#endif

 

            var sri = App.GetResourceStream(new Uri("MockupLincenseInfo.xml", UriKind.Relative));

            System.Xml.Linq.XDocument doc = System.Xml.Linq.XDocument.Load(sri.Stream);

            string xml = doc.ToString();

 

            MockIAP.SetListingInformation(1, "en-us", "IAP Sample by Alex Golesh", "$12.99", "IAP Sample");

 

            MockIAP.PopulateIAPItemsFromXml(xml);

#endif

Note: DONT_PRESERVE_LICENSE_INFO is pragma defined in the app and controls whether mock library preserves previous purchases between debug runs. Preserving cache makes library behave as real marketplace does.

When purchasing using mock library it presents message box as follows:

image

 

That’s it for now. Next time I will blog about Wallet functionality.

 

Stay tuned,

Alex

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

2 comments

  1. EdyJanuary 19, 2013 ב 8:42

    Steve,Great article. I reeembmr in a business class that I took a long time ago, the lesson that really stood out is spend less time worrying about what font to use on your business card and more time worrying about what makes your product or service better than the others, and how you’re going to get that message out to the public. Or as you say, there’s a lot you can worry about after you hit critical mass and are making money. Scott

    Reply
  2. Ashish GuptaJanuary 22, 2013 ב 5:03

    Hi sir

    Please provide the source code of this example..

    Reply