Click here to Skip to main content
14,355,411 members

Monetize your App in Microsoft Store

Rate this:
2.83 (4 votes)
Please Sign up or sign in to vote.
2.83 (4 votes)
22 Jul 2019CPOL
Monetize your UWP, WPF and Winform app in Microsoft Store with a Durable add-on

Table of Contents

Introduction

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.

Monetize Your App

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.

// All Store operations are performed on a StoreContext
private StoreContext storeContext = null;

// A collection of available store-managed add-ons
public ObservableCollection<StoreProduct> AddOns { get; set; } = 
    new ObservableCollection<StoreProduct>();

// Error code for Store
static int IAP_E_UNEXPECTED = unchecked((int)0x803f6107);

// Keep track if the add-on is purchased
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.

// This interface definition is necessary because this is a non-universal
// app. This is part of enabling the Store UI purchase flow.
[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)
{
    //https://blogs.msdn.microsoft.com/appconsult/2016/11/03/
    //        desktop-bridge-identify-the-applications-context/
    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.

// Check the license and get the addOns when needed
public async Task<bool> InitializeAddOnsList()
{
    // Check the license to validate if we purchased the durable
    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
    {
        // Create a list of the product add-ons
        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;
}

// Purchase a given product
public async Task<StorePurchaseResult> PurchaseAddOn(StoreProduct product)
{
    // The next two lines are only required because this is a non-universal app
    // so we have to initialize the store context with the main window handle
    // manually. This happens automatically in a Universal app.
    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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Shao Voon Wong
Software Developer (Senior)
Singapore Singapore
Shao Voon is from Singapore. CodeProject awarded him a MVP award in recognition of his article contributions in 2019. In his spare time, he prefers to writing application based on 3rd party library than writing his own library. His interest lies primarily in computer graphics, software optimization, security and Agile methodologies.

You can reach him by sending a message on CodeProject or at his Coding Tidbit Blog!

Comments and Discussions

 
-- There are no messages in this forum --
Article
Posted 22 Jul 2019

Tagged as

Stats

2.3K views
5 bookmarked