Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 219
Steve Wortham Avatar asked Oct 17 '25 15:10

Steve Wortham


1 Answers

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.

like image 153
Steve Wortham Avatar answered Oct 21 '25 01:10

Steve Wortham



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!