Implementing In-App Billing in .NET MAUI

πŸ’³ Implementing In-App Billing in .NET MAUI

Building a Cross-Platform Purchase System for Mobile Apps

Monetization is one of the most critical parts of modern mobile applications. Whether you’re selling:

  • ⭐ Premium features
  • πŸ“¦ Consumables
  • πŸ”„ Subscriptions
  • πŸš€ Pro upgrades …you eventually need a reliable In-App Billing system. With .NET MAUI, implementing purchases introduces unique challenges:
  • Different store ecosystems πŸͺ
  • Platform-specific APIs πŸ“±
  • Purchase restoration πŸ”„
  • Receipt validation πŸ”
  • Offline scenarios πŸ“Ά In this guide, we’ll build a clean, scalable, production-ready In-App Billing architecture for .NET MAUI.

🧠 Understanding the Ecosystem

In-app purchases are platform-dependent:

Platform Billing System
Android πŸ€– Google Play Billing
iOS 🍏 StoreKit
Windows πŸͺŸ Microsoft Store

Each one has:

  • Different purchase flows
  • Different receipt formats
  • Different subscription handling πŸ‘‰ Your goal in MAUI is to abstract all of that complexity.

πŸ—οΈ Architecture Overview

A clean billing architecture should look like this:

UI Layer
    ↓
IInAppBillingService
    ↓
Platform Billing Providers
    ↓
Google Play / App Store / Microsoft Store

🧩 Why You Should NEVER Call Store APIs Directly from UI

Bad:

Button β†’ Google Billing API

Good:

Button β†’ Billing Service β†’ Store Provider

This separation gives you:

  • πŸ§ͺ Testability
  • πŸ”„ Easier maintenance
  • πŸ“± Platform abstraction
  • πŸš€ Scalability

πŸ“¦ Product Types

Understanding billing types is critical.


πŸ›’ Consumables

Can be purchased multiple times. Examples:

  • Coins
  • Gems
  • Energy

πŸ”“ Non-Consumables

Purchased once forever. Examples:

  • Remove Ads
  • Premium Upgrade

πŸ”„ Subscriptions

Recurring billing. Examples:

  • Monthly Premium
  • Cloud Sync
  • Pro Features

βš™οΈ Step 1: Install Billing Library

Most MAUI apps use:

  • Plugin.InAppBilling

πŸ“¦ Install

dotnet add package Plugin.InAppBilling

🧩 Step 2: Create Billing Abstraction

public interface IInAppBillingService
{
    Task<IEnumerable<IProduct>> GetProductsAsync();
    Task<bool> PurchaseAsync(string productId);
    Task<bool> RestorePurchasesAsync();
}

βš™οΈ Step 3: Service Implementation

using Plugin.InAppBilling;

public class InAppBillingService : IInAppBillingService
{
    public async Task<IEnumerable<IProduct>> GetProductsAsync()
    {
        var billing = CrossInAppBilling.Current;

        var connected = await billing.ConnectAsync();

        if (!connected)
            return Enumerable.Empty<IProduct>();

        return await billing.GetProductInfoAsync(
            ItemType.InAppPurchase,
            "premium_upgrade",
            "pro_subscription");
    }

    public async Task<bool> PurchaseAsync(string productId)
    {
        var billing = CrossInAppBilling.Current;

        await billing.ConnectAsync();

        var purchase = await billing.PurchaseAsync(
            productId,
            ItemType.InAppPurchase);

        return purchase != null;
    }

    public async Task<bool> RestorePurchasesAsync()
    {
        var billing = CrossInAppBilling.Current;

        await billing.ConnectAsync();

        var purchases = await billing.GetPurchasesAsync(ItemType.InAppPurchase);

        return purchases?.Any() == true;
    }
}

πŸ“± Step 4: Register in MAUI DI

builder.Services.AddSingleton<IInAppBillingService, InAppBillingService>();

🎨 Step 5: MAUI ViewModel Integration

public partial class StoreViewModel : ObservableObject
{
    private readonly IInAppBillingService _billingService;

    [ObservableProperty]
    private bool isPremium;

    public StoreViewModel(IInAppBillingService billingService)
    {
        _billingService = billingService;
    }

    [RelayCommand]
    public async Task BuyPremium()
    {
        IsPremium = await _billingService.PurchaseAsync("premium_upgrade");
    }
}

πŸ” Purchase Validation (VERY IMPORTANT)

One of the biggest mistakes developers make:

"Purchase succeeded locally, so unlock premium"

❌ Dangerous.


🧠 Why Local Validation Is Not Enough

Attackers can:

  • Modify APKs
  • Fake responses
  • Bypass local checks

βœ… Correct Production Approach

Device β†’ Your Backend β†’ App Store Validation

Your server should:

  • Validate receipts
  • Store entitlements
  • Handle subscription expiration
  • Detect fraud

🍏 iOS Receipt Validation

Uses:

  • App Store receipts
  • StoreKit APIs

πŸ€– Android Validation

Uses:

  • Google Play Developer API
  • Purchase tokens

βš–οΈ Local Validation vs Server Validation

Validation Type Security
Local only ❌ Weak
Server-side βœ… Strong

πŸ”„ Restoring Purchases

Critical for:

  • Reinstallations
  • Device changes
  • Subscription recovery

Example

await billing.GetPurchasesAsync(ItemType.InAppPurchase);

⚠️ Common MAUI Billing Pitfalls

❌ Blocking UI Thread

Never do heavy purchase work on UI thread.


❌ Forgetting DisconnectAsync()

await billing.DisconnectAsync();

Store connections should be cleaned up.


❌ Hardcoding Product IDs Everywhere

Instead:

public static class Products
{
    public const string Premium = "premium_upgrade";
}

πŸ“‘ Subscription Handling

Subscriptions introduce complexity:

  • Renewals
  • Grace periods
  • Expiration
  • Trial periods

🧠 Recommended Strategy

Store entitlements separately:

public class UserEntitlement
{
    public bool IsPremium { get; set; }
    public DateTime? ExpirationDate { get; set; }
}

⚑ Offline Scenarios

What if the user is offline? Good strategy:

  • Cache entitlements locally
  • Sync when online
  • Gracefully degrade premium access

🧱 Advanced Architecture (PRO Level)

πŸ”„ Event-Driven Billing

Instead of tightly coupling UI:

PurchaseCompletedEvent
SubscriptionExpiredEvent

This scales much better.


πŸ“Š Analytics Integration

Track:

  • Conversion rate πŸ“ˆ
  • Failed purchases ⚠️
  • Subscription churn πŸ”„

🧩 Feature Flags

Premium features should be abstracted:

if (_featureService.IsPremiumEnabled)
{
    // Unlock feature
}

βš–οΈ Plugin vs Native APIs

Approach Pros Cons
Plugin.InAppBilling Fast, cross-platform Less control
Native APIs Full flexibility Much more complexity

πŸš€ Real-World Performance Tips

⚑ Cache Product Metadata

Avoid fetching products repeatedly.


⚑ Lazy Load Store

Only initialize billing when needed.


⚑ Handle Store Failures Gracefully

Stores fail more often than people think.


πŸ”— Reference Links


🧠 Key Takeaways

  • Billing architecture matters more than purchase code πŸ’³
  • Abstract store complexity behind services 🧩
  • Always validate purchases server-side πŸ”
  • Subscriptions are significantly harder than one-time purchases πŸ”„
  • Offline scenarios must be considered πŸ“Ά

πŸ”š Final Thoughts

In-App Billing is deceptively complex. At first glance it seems like:

"User clicks button β†’ purchase succeeds"

But production systems require:

  • Security πŸ”
  • Synchronization πŸ”„
  • Entitlement management 🧠
  • Platform abstraction πŸ“±
  • Failure handling ⚠️

With .NET MAUI, you can build a clean, scalable purchase system that feels native across every platform. And when done correctly, your billing layer becomes a core part of your app’s architectureβ€”not just a payment button. πŸš€

An unhandled error has occurred. Reload πŸ—™