How can I allow in-app purchases through my .NET MAUI application?

2.1k Views Asked by At

I'm looking for documentation on this and I'm not finding it.

The main things I need to accomplish are:

  • Allow the user to purchase an add-on through the Microsoft Store.
  • Determine if they've already purchased an add-on so the appropriate application features can be enabled.

These two things can be accomplished easily for a UWP app as described here: https://learn.microsoft.com/en-us/windows/uwp/monetize/enable-subscription-add-ons-for-your-app#code-examples

But I can't find an equivalent for .NET MAUI.

1

There are 1 best solutions below

1
On

So in fact you can use the UWP code examples from Microsoft. The trick is to wrap everything that's Windows specific in a statement like this:

#if WINDOWS
    ...
#endif

So my code looks like this:

#if WINDOWS
    using Windows.Services.Store;
#endif

namespace MyProject.Data
{
    public class AddOnService
    {
#if WINDOWS
            private StoreContext context = null;
            StoreProduct subscriptionStoreProduct;
    
            // Assign this variable to the Store ID of your subscription add-on.
            private string subscriptionStoreId = "INSERT-STORE-ID";  
    
            public async Task<bool> SetupSubscriptionInfoAsync()
            {
                if (context == null)
                {
                    context = StoreContext.GetDefault();
                    // If your app is a desktop app that uses the Desktop Bridge, you
                    // may need additional code to configure the StoreContext object.
                    // For more info, see https://aka.ms/storecontext-for-desktop.
                }
    
                bool userOwnsSubscription = await CheckIfUserHasSubscriptionAsync();
                if (userOwnsSubscription)
                {
                    // Unlock all the subscription add-on features here.
                    return true;
                }
    
                // Get the StoreProduct that represents the subscription add-on.
                subscriptionStoreProduct = await GetSubscriptionProductAsync();
                if (subscriptionStoreProduct == null)
                {
                    return true;
                }
    
                // Check if the first SKU is a trial and notify the customer that a trial is available.
                // If a trial is available, the Skus array will always have 2 purchasable SKUs and the
                // first one is the trial. Otherwise, this array will only have one SKU.
                StoreSku sku = subscriptionStoreProduct.Skus[0];
                if (sku.SubscriptionInfo.HasTrialPeriod)
                {
                    // You can display the subscription trial info to the customer here. You can use 
                    // sku.SubscriptionInfo.TrialPeriod and sku.SubscriptionInfo.TrialPeriodUnit 
                    // to get the trial details.
                }
                else
                {
                    // You can display the subscription purchase info to the customer here. You can use 
                    // sku.SubscriptionInfo.BillingPeriod and sku.SubscriptionInfo.BillingPeriodUnit
                    // to provide the renewal details.
                }
    
                // Prompt the customer to purchase the subscription.
                await PromptUserToPurchaseAsync();
    
                return false;
            }
    
            private async Task<StoreProduct> GetSubscriptionProductAsync()
            {
                // Load the sellable add-ons for this app and check if the trial is still 
                // available for this customer. If they previously acquired a trial they won't 
                // be able to get a trial again, and the StoreProduct.Skus property will 
                // only contain one SKU.
                StoreProductQueryResult result =
                    await context.GetAssociatedStoreProductsAsync(new string[] { "Durable" });
    
                if (result.ExtendedError != null)
                {
                    System.Diagnostics.Debug.WriteLine("Something went wrong while getting the add-ons. " +
                        "ExtendedError:" + result.ExtendedError);
                    return null;
                }
    
                // Look for the product that represents the subscription.
                foreach (var item in result.Products)
                {
                    StoreProduct product = item.Value;
                    if (product.StoreId == subscriptionStoreId)
                    {
                        return product;
                    }
                }
    
                System.Diagnostics.Debug.WriteLine("The subscription was not found.");
                return null;
            }
    
            private async Task<bool> CheckIfUserHasSubscriptionAsync()
            {
                StoreAppLicense appLicense = await context.GetAppLicenseAsync();
    
                // Check if the customer has the rights to the subscription.
                foreach (var addOnLicense in appLicense.AddOnLicenses)
                {
                    StoreLicense license = addOnLicense.Value;
                    if (license.SkuStoreId.StartsWith(subscriptionStoreId))
                    {
                        if (license.IsActive)
                        {
                            // The expiration date is available in the license.ExpirationDate property.
                            return true;
                        }
                    }
                }
    
                // The customer does not have a license to the subscription.
                return false;
            }
    
            private async Task PromptUserToPurchaseAsync()
            {
                // Request a purchase of the subscription product. If a trial is available it will be offered 
                // to the customer. Otherwise, the non-trial SKU will be offered.
                StorePurchaseResult result = await subscriptionStoreProduct.RequestPurchaseAsync();
    
                // Capture the error message for the operation, if any.
                string extendedError = string.Empty;
                if (result.ExtendedError != null)
                {
                    extendedError = result.ExtendedError.Message;
                }
    
                switch (result.Status)
                {
                    case StorePurchaseStatus.Succeeded:
                        // Show a UI to acknowledge that the customer has purchased your subscription 
                        // and unlock the features of the subscription. 
                        break;
    
                    case StorePurchaseStatus.NotPurchased:
                        System.Diagnostics.Debug.WriteLine("The purchase did not complete. " +
                            "The customer may have cancelled the purchase. ExtendedError: " + extendedError);
                        break;
    
                    case StorePurchaseStatus.ServerError:
                    case StorePurchaseStatus.NetworkError:
                        System.Diagnostics.Debug.WriteLine("The purchase was unsuccessful due to a server or network error. " +
                            "ExtendedError: " + extendedError);
                        break;
    
                    case StorePurchaseStatus.AlreadyPurchased:
                        System.Diagnostics.Debug.WriteLine("The customer already owns this subscription." +
                                "ExtendedError: " + extendedError);
                        break;
                }
            }
#else
        public async Task<bool> SetupSubscriptionInfoAsync()
        {
            return false;
        }
#endif
    }
}

I'll be modifying this some to suit my needs but I was able to debug it and step through the code to verify that the code is indeed executing as expected on Windows.