Table of Contents
This article is a follow-up from Bring Your Existing Application to Microsoft Store. We'll look at how to monetize your free-to-use UWP, WPF and Winform app with a Durable add-on. There are 2 main types of add-ons: Durable and Consumable. You can look at Durable as an add-on with fixed monetary value while Consumable value can get used up over time or through consumption of service or virtual item. We'll look at Consumable in a future article. The app should be free to use if it is to be popular as quickly as possible. Premium feature can be unlocked through Durable add-on. Of course, you can keep your app behind a paywall but it is much harder to go viral that route.
Get this nuget package in your Visual Studio project: DesktopBridge.Helpers
to determine UWP mode using IsRunningAsUwp()
. Add library reference to Windows.winmd
to make the UWP API visible to your WPF and Winform app. The purchase code should be the same for UWP because we are calling UWP APIs in the first place, with the exception of IInitializeWithWindow
does not need to be declared in true UWP app.
C:\Program Files(x86)\Windows Kits\10\UnionMetadata\Windows.winmd
For your app to use UWP, adding Windows.winmd is not enough. You have to add System.Runtime.WindowsRuntime.dll.
C:\Program Files(x86)\
Reference Assemblies\Microsoft\Framework\.NETCore\v4.5\System.Runtime.WindowsRuntime.dll
Readers may have this question running in their minds now: What kind of sorcery is this? .NET Core 4.5?! I do not have the answer to your question. It could be the developer who wrote this DLL, is a time traveller. Strange enough, it worked for my WPF app written in .NET Framework 4.6.1!
To begin writing the code, add using Windows.Services.Store
namespace:
using Windows.Services.Store;
For the member variables, we need to have StoreContext
object to perform the store operations. And an ObservableCollection
of StoreProduct
type to store all the add-ons retrieved from the StoreContext
. Next, we have a error code, 0x803f6107
, for unexpected operation. And lastly, a variable, m_IsPurchased
, to store whether our add-on is purchased. m_IsPurchased
is initialized to false
. For my app, I only have 1 add-on, so I only have 1 variable to store it.
private StoreContext storeContext = null;
public ObservableCollection<StoreProduct> AddOns { get; set; } =
new ObservableCollection<StoreProduct>();
static int IAP_E_UNEXPECTED = unchecked((int)0x803f6107);
private bool m_IsPurchased = false;
We need to put this IInitializeWithWindow
interface inside our WPF or Winform application so that the app can invoke UWP window. Remember the Purchase Window shown to the user on our behalf, is a UWP window.
[ComImport]
[Guid("3E68D4BD-7135-4D10-8018-9FB6D9F33FA1")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IInitializeWithWindow
{
void Initialize(IntPtr hwnd);
}
After everything is initialized, we need to check whether we are running under UWP mode and initialize the storeContext
object and retrieve the add-on from storeContext
in InitializeAddOnsList()
.
private async void Host_Loaded(object sender, RoutedEventArgs e)
{
DesktopBridge.Helpers helper = new DesktopBridge.Helpers();
if (helper.IsRunningAsUwp())
{
storeContext = StoreContext.GetDefault();
await InitializeAddOnsList();
}
}
In InitializeAddOnsList()
, we get our license from storeContext
. If there is a license associated with "9Nxxxxx"
, retrieved and it is active, m_IsPurchased
is set to true
, else we retrieve all the add-ons associated with this product. Remember to replace "9Nxxxxx"
with your add-on Store ID. Note that the filterList
is set to "Durable": when your add-on is a Consumable, set or add "Consumable" to filterList
. The difference between Durable and Consumable add-on is Consumable monetary value associated with it can be reduced to nothing through consumption of the add-on while Durable monetary value stays the same through it can be set to expire after a certain amount of elapsed period. My Durable add-on does not expire here, in other words, perpetual.
public async Task<bool> InitializeAddOnsList()
{
var appLicense = await storeContext.GetAppLicenseAsync();
var iapLicense = appLicense.AddOnLicenses.Select(l => l.Value)
.FirstOrDefault(l => l.SkuStoreId.StartsWith("9Nxxxxx"));
if ((iapLicense != null) && iapLicense.IsActive)
{
m_IsPurchased = true;
}
else
{
string[] filterList = new string[] { "Durable" };
var addOns = await storeContext.GetAssociatedStoreProductsAsync(filterList);
if (addOns.ExtendedError != null)
{
if (addOns.ExtendedError.HResult == IAP_E_UNEXPECTED)
{
return false;
}
}
foreach (var addOn in addOns.Products)
{
StoreProduct product = addOn.Value;
AddOns.Add(product);
}
}
return true;
}
We call Purchase()
which in turn calls PurchaseAddOn()
. initWindow
is casted from storeContext
object and is initialized with our main WPF window as its parent. When the purchase is successful, set m_IsPurchased
to true
.
private async Task<StorePurchaseResult> Purchase()
{
if (AddOns.Count > 0)
{
return await this.PurchaseAddOn((StoreProduct)AddOns[0]);
}
return null;
}
public async Task<StorePurchaseResult> PurchaseAddOn(StoreProduct product)
{
IInitializeWithWindow initWindow = (IInitializeWithWindow)(object)storeContext;
initWindow.Initialize(System.Diagnostics.Process.GetCurrentProcess().MainWindowHandle);
var result = await storeContext.RequestPurchaseAsync(product.StoreId);
if (result.Status == StorePurchaseStatus.Succeeded)
{
m_IsPurchased = true;
}
return result;
}
Other Articles in the Bring Your... Series
History
- 22nd July, 2019: Initial version