The Universal Windows Platform supports In-App Purchases (IAP). This feature was already available in Windows 8/8.1, but I have not used it before. When I implemented this feature in one of my Windows 10 UWP app I had some trouble testing it. So this post summarizes the findings.
These are the two most important classes (both of them from the Windows.ApplicationModel.Store namespace) :
- CurrentApp: This must be used in production when you submit your app to the store
- CurrentAppSimulation: This is for testing, use this before you submit your app to test the features and then change it to CurrentApp before you generate your store packages.
Implementing a feature with IAP
In the sample code I will use the CurrentAppSimulation class. The first step I suggest to do is to define an application scope resource to the licencemanager. I do this in the App.xaml.cs file in the OnLaunched method:
var licenseInformation = CurrentApp.LicenseInformation;
Application.Current.Resources.Add("licenseInformation", licenseInformation);
In this sample I use IAP to remove Ads from the app. This means when a user purchased this feature then there will be no ads shown in the app. For this in the codebehind file I do the following:
var licenseInformation = Application.Current.Resources["licenseInformation"]
as LicenseInformation;
if (licenseInformation != null &&
licenseInformation.ProductLicenses["RemoveAds"].IsActive)
{
AdGrid.Visibility = Visibility.Collapsed;
}
else
{
AdGrid.Visibility = Visibility.Visible;
}
So the licenseInformation.ProductLicenses["RemoveAds"].IsActive line tells wheter the "RemoveAds" feature is already purchased by the current user. I do this in the code-behind file, because for me this is purely UI logic (either I show Ads, or I don't). If you use MVVM and implement a more complex feature you can obviously shift this to a Model or a ViewModel class.
Ok, so how to purchase an IAP item?
var licenseInformation = Application.Current.Resources["licenseInformation"]
as LicenseInformation;
var loader =
Windows.ApplicationModel.Resources.ResourceLoader.GetForCurrentView();
if (licenseInformation != null &&
!licenseInformation.ProductLicenses["RemoveAds"].IsActive)
{
try
{
//This must be commented out in production
var file = await Windows.ApplicationModel.Package.Current.
InstalledLocation.GetFileAsync(@"Testing\WindowsStoreProxy.xml");
await CurrentAppSimulator.ReloadSimulatorAsync(file);
var result = await CurrentApp.RequestProductPurchaseAsync("RemoveAds");
if (result.Status == ProductPurchaseStatus.Succeeded)
{
MessageDialog md =
new MessageDialog(loader.GetString("AdsAreNowDisabled"));
await md.ShowAsync();
}
else
{
MessageDialog md =
new MessageDialog(loader.GetString("STWentWrong"));
await md.ShowAsync();
}
}
catch (Exception exception)
{
// The in-app purchase was not completed because
// an error occurred.
}
}
else
{
MessageDialog md = new MessageDialog(loader.GetString("AlreadyPaid"));
await md.ShowAsync();
}
It's very simple... the var result = await CurrentApp.RequestProductPurchaseAsync("RemoveAds") line basically shows the UI which let's the user buy the feature. Now this works in production with the CurrentApp class, but for me it did not work with the CurrentAppSimulation. As MSDN states there is a C:\Users\<username>\AppData\Local\Packages\<app package folder>\LocalState\Microsoft\Windows Store\ApiData\WindowsStoreProxy.xml file which stores the current "fake store configuration" of the app for testing. I basically made a copy of that file and pasted it into the Visual Studio Project and modified it as it was needed.
This file is imported by the CurrentAppSimulator.ReloadSimulatorAsync call at the beginning of the code snippet:
var file = await Windows.ApplicationModel.Package.Current.InstalledLocation.GetFileAsync(@"Testing\WindowsStoreProxy.xml");
await CurrentAppSimulator.ReloadSimulatorAsync(file);
When you use CurrentAppSimulation and the execution hits the CurrentApp.RequestProductPurchaseAsync part then instead of the UI where the user can by the IAP this window is shown:
Now I have no idea what the intention behind this is, but what I noticed is that it basically controls the return value of the underlying Windows Runtime native function. If you let it return S_OK then it returns success, otherwise the RequestProductPurchaseAsync C# method throws an exception (marshalled from the selected Windows Runtime error...). I think this window is not intentional and in future releases we see something else at this point.
I (and other people, see this) faced another problem: by default the status of the result was never ProductPurchaseStatus.Succeeded. It turned out the the default WindowsStoreProxy.xml defines that the app is in trial mode, and in that mode IAPs just do not work. And this is why I always make a copy of the WindowsStoreProxy.xml for every project: that way I can change it for my needs when I want to test IAPs. So here is the default one I used for this test:
<?xml version="1.0" encoding="utf-16" ?>
<CurrentApp>
<ListingInformation>
<App>
<AppId>00000000-0000-0000-0000-000000000000</AppId>
<LinkUri>
http://apps.microsoft.com/webpdp/app/00000000-0000-0000-0000-000000000000
</LinkUri>
<CurrentMarket>en-US</CurrentMarket>
<AgeRating>3</AgeRating>
<MarketData xml:lang="en-US">
<Name>AppName</Name>
<Description>AppDescription</Description>
<Price>1.00</Price>
<CurrencySymbol>$</CurrencySymbol>
<CurrencyCode>USD</CurrencyCode>
</MarketData>
</App>
<Product ProductId="RemoveAds" LicenseDuration="1"
ProductType="Durable">
<MarketData xml:lang="en-US">
<Name>RemoveAds</Name>
<Price>1.00</Price>
<CurrencySymbol>$</CurrencySymbol>
<CurrencyCode>USD</CurrencyCode>
</MarketData>
</Product>
</ListingInformation>
<LicenseInformation>
<App>
<IsActive>true</IsActive>
<IsTrial>false</IsTrial>
</App>
<Product ProductId="1">
<IsActive>true</IsActive>
</Product>
</LicenseInformation>
<ConsumableInformation>
<Product ProductId="RemoveAds"
TransactionId="10000000-0000-0000-0000-000000000000"
Status="Active" />
</ConsumableInformation>
</CurrentApp>
Pay attention to the <IsTrial> tag! This is the most important part for IAPs: Make sure you set it to false when you test IAPs. The other thing is that I defined my "RemoveAds" product in this file. It surprised me, but it also worked without it (obviously the default template do not contain that..), but to be on the safe side I also defined that in the custom WindowsStoreProxy.xml file.
So that's it, with this you can test your IAPs. Make sure you switch the CurrentApSimulations to CurrentApp before you generate your store package.