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. π
