Kebechet.Maui.RevenueCat.InAppBilling 10.1.0

Prefix Reserved
dotnet add package Kebechet.Maui.RevenueCat.InAppBilling --version 10.1.0
                    
NuGet\Install-Package Kebechet.Maui.RevenueCat.InAppBilling -Version 10.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Kebechet.Maui.RevenueCat.InAppBilling" Version="10.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Kebechet.Maui.RevenueCat.InAppBilling" Version="10.1.0" />
                    
Directory.Packages.props
<PackageReference Include="Kebechet.Maui.RevenueCat.InAppBilling" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Kebechet.Maui.RevenueCat.InAppBilling --version 10.1.0
                    
#r "nuget: Kebechet.Maui.RevenueCat.InAppBilling, 10.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Kebechet.Maui.RevenueCat.InAppBilling@10.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Kebechet.Maui.RevenueCat.InAppBilling&version=10.1.0
                    
Install as a Cake Addin
#tool nuget:?package=Kebechet.Maui.RevenueCat.InAppBilling&version=10.1.0
                    
Install as a Cake Tool

"Buy Me A Coffee"

Maui.RevenueCat.InAppBilling

NuGet Version NuGet Downloads Last updated (main) Twitter

A .NET MAUI wrapper library for RevenueCat in-app purchases. Provides a unified C# API that abstracts away the need for you to use platform-specific code from Android and iOS native bindings.

Features

  • Unified API for iOS, Android and Mac Catalyst in-app purchases
  • Subscription and one-time purchase support
  • User authentication (anonymous and identified users)
  • Subscription status and entitlement checking
  • Trial/intro discount eligibility
  • Subscriber attributes management
  • Test Store test_… keys work in Release builds on iOS - the shipped xcframework is built from source with BYPASS_SIMULATED_STORE_RELEASE_CHECK, so it does not force-close the app
  • Stub implementation for Windows (for development convenience)

Installation

dotnet add package Kebechet.Maui.RevenueCat.InAppBilling

Quick Start

1. Register the service

In your MauiProgram.cs:

builder.Services.AddRevenueCatBilling();

2. Inject and initialize

In App.xaml.cs, inject IRevenueCatBilling and initialize in OnStart():

public partial class App : Application
{
    private readonly IRevenueCatBilling _revenueCat;

    public App(IRevenueCatBilling revenueCat)
    {
        InitializeComponent();
        _revenueCat = revenueCat;
    }

    protected override void OnStart()
    {
        var revenueCatApiKey = string.Empty;

#if __ANDROID__
        revenueCatApiKey = "<your-android-api-key>";
#elif __IOS__
        revenueCatApiKey = "<your-ios-api-key>";
#endif

        _revenueCat.Initialize(revenueCatApiKey);
        base.OnStart();
    }
}

Important: Initialize must be called in OnStart(), not in the constructor.

Tip: If you already know your user's ID at startup, use Initialize(revenueCatApiKey, appUserId) instead - RevenueCat then never creates an anonymous user ($RCAnonymousID:...), so your custom ID won't show up as an alias of an anonymous customer.

Test Store API keys

RevenueCat's Test Store lets you exercise the full purchase flow without configuring App Store Connect / Play Console products. Test Store keys start with test_.

This wrapper ships an iOS xcframework built from source with the BYPASS_SIMULATED_STORE_RELEASE_CHECK Swift compilation flag, so a test_… key works on iOS Release builds too — it does not force-close the app. No code changes are required on your side; just pass the test_… key to Initialize(...).

On Android no such rebuild is needed: the RevenueCat SDK ships as a normal AAR (Kotlin bytecode), so its test-key check runs against the consuming app's build at runtime rather than being compiled into the library — a prebuilt AAR doesn't carry the always-on guard the prebuilt iOS framework did. Test Store support does require RevenueCat Android SDK 9.9.0+; this binding ships upstream SDK 11.1.0 (binding package 11.1.0), so test_… keys work out of the box in Debug builds (see issue #95, resolved by the 10.1.2 SDK bump). In a Release build of your app, RevenueCat refuses the key with a ConfigurationError ("Test Store API key used in release build"). The same "don't ship a test_ key to production" guidance applies on both platforms.

See src/Maui.RevenueCat.iOS/README.md for the build-time details and issue #116 for context.

Debug logs

RevenueCat's debug logs are off by default. Enable them explicitly, for example only in your Debug builds:

#if DEBUG
builder.Services.AddRevenueCatBilling(forceEnableDebugLogs: true);
#else
builder.Services.AddRevenueCatBilling();
#endif

Where to read them:

  • Android: logcat, filter by Purchases.
  • iOS: the device's system log, not the Visual Studio output window. Open it in Xcode → Window → Devices and Simulators → Open Console, or in Console.app on the Mac, and filter by Purchases.

When offerings come back empty with a configuration error, the logs show which product identifiers RevenueCat requested from the store. See Why are offerings empty? and issue #127.

API Reference

Initialization & State

Method Description
Initialize(string apiKey) Initialize RevenueCat with your platform-specific API key
Initialize(string apiKey, string appUserId) Initialize with a custom App User ID - no anonymous user is created, so no alias is added later
IsInitialized() Check if the SDK has been initialized
IsAnonymous() Check if current user is anonymous
GetAppUserId() Get the current user ID

Offerings & Products

Method Description
GetOfferings(bool forceRefresh = false) Fetch available offerings and packages. Value is List<OfferingDto>
CheckTrialOrIntroDiscountEligibility(List<string> identifiers) Check eligibility for trials/intro pricing. Value is Dictionary<string, IntroElegibilityStatus>. Apple platforms only (iOS and Mac Catalyst); throws NotImplementedException on Android

Purchases

Method Description
PurchaseProduct(PackageDto package) Initiate a purchase flow. Returns PurchaseResultDto - Value is the refreshed CustomerInfoDto, plus a Transaction
GetActiveSubscriptions() Get list of active subscription identifiers. Value is List<string>
GetAllPurchasedIdentifiers() Get all purchased product identifiers. Value is List<string>
GetPurchaseDateForProductIdentifier(string productSku) Get purchase date for a specific product. Value is DateTime?; null means never purchased
RestoreTransactions() Restore previous purchases. Value is CustomerInfoDto

User Management

Method Description
Login(string appUserId) Log in an identified user. Value is CustomerInfoDto
Logout() Log out and create anonymous user. Value is CustomerInfoDto
GetCustomerInfo(CustomerInfoFetchPolicy fetchPolicy = CachedOrFetched) Get current customer info and entitlements. Value is CustomerInfoDto. The default may return stale cached data - see Customer Info Caching
GetManagementSubscriptionUrl() Get URL for subscription management. Value is string?; null means no store-managed subscription
ShowManageSubscriptions() Show the store's own subscription management screen: the App Store's sheet on iOS and Mac Catalyst, the subscription's Google Play page on Android. The result carries no Value. On Android it fails with UnsupportedError when the user has no store-managed subscription, including every Test Store subscription

Customer Info Caching

The RevenueCat SDK caches customer info on the device and treats it as stale after 5 minutes while the app is in the foreground (RevenueCat docs), but only after 25 hours while it is in the background (an iOS sandbox build keeps 5 minutes). fetchPolicy decides whether GetCustomerInfo answers from that cache:

CustomerInfoFetchPolicy Behavior
CachedOrFetched (default) Returns the cache even when stale; waits for a fetch only when nothing is cached. A stale cache is also refreshed in the background, but this call still returns the stale data
FetchCurrent Always fetches from RevenueCat. Fails when offline
NotStaleCachedOrFetched Returns the cache while it is fresh, otherwise fetches. Never returns stale data - fails instead when the fetch fails
FromCacheOnly Never fetches. Fails when nothing is cached

A failed call sets Error to CustomerInfoError when FromCacheOnly finds nothing cached. A fetch that fails because the device is offline sets NetworkError on Android and OfflineConnectionError on iOS.

// e.g. after the user bought on the web, so the device cache cannot know about it yet
var customerInfoResult = await _revenueCat.GetCustomerInfo(CustomerInfoFetchPolicy.FetchCurrent);

PurchaseProduct(), RestoreTransactions() and Logout() already return fresh customer info, and so does Login() when it switches to a different app user ID. Login() with the ID that is already logged in reads with CachedOrFetched instead, so it can return stale data - follow it with GetCustomerInfo(CustomerInfoFetchPolicy.FetchCurrent) when that matters. GetActiveSubscriptions(), GetAllPurchasedIdentifiers(), GetPurchaseDateForProductIdentifier() and GetManagementSubscriptionUrl() always read with CachedOrFetched.

Subscriber Attributes

Method Description
SetEmail(string email) Set user's email
SetDisplayName(string name) Set user's display name
SetPhoneNumber(string phone) Set user's phone number
SetAttributes(IDictionary<string, string> attributes) Set custom attributes

Example: Complete Purchase Flow

public class PurchaseService
{
    private readonly IRevenueCatBilling _revenueCat;

    public PurchaseService(IRevenueCatBilling revenueCat)
    {
        _revenueCat = revenueCat;
    }

    public async Task<bool> PurchaseSubscription()
    {
        var offeringsResult = await _revenueCat.GetOfferings();
        var offerings = offeringsResult.Value;
        if (offeringsResult.IsError || offerings is null || !offerings.Any())
            return false;

        var defaultOffering = offerings.FirstOrDefault(o => o.IsCurrent);
        var monthlyPackage = defaultOffering?.AvailablePackages
            .FirstOrDefault(p => p.Identifier == "monthly");

        if (monthlyPackage == null)
            return false;

        var result = await _revenueCat.PurchaseProduct(monthlyPackage);

        if (result.IsSuccess)
        {
            // Purchase successful
            return true;
        }

        if (result.Error == PurchaseErrorStatus.PurchaseCancelledError)
        {
            // User cancelled - not an error
            return false;
        }

        // Handle other errors
        Console.WriteLine($"Purchase failed: {result.Error}");
        return false;
    }

    public async Task<bool> HasActiveSubscription(string entitlementId)
    {
        var customerInfoResult = await _revenueCat.GetCustomerInfo();
        return customerInfoResult.Value?.ActiveSubscriptions
            .Any(e => e == entitlementId) ?? false;
    }
}

Platform Support

Platform Support
Android Full implementation
iOS Full implementation
Windows Stub (returns defaults)
MacCatalyst Full implementation (shares the iOS implementation)

Stub implementations return a successful result (IsSuccess == true, no Error) whose Value is an empty collection, an empty CustomerInfoDto, string.Empty for the storefront country code, or null for GetManagementSubscriptionUrl() and GetPurchaseDateForProductIdentifier(). This allows you to build and test on Windows without platform conditionals.

Error Handling

The library follows a non-throwing approach for runtime errors:

  • Exceptions are thrown only for developer mistakes (e.g., calling methods before Initialize())
  • Runtime errors (network issues, store problems, etc.) come back as a failed result

Every asynchronous member returns a named result DTO built on DataResult<TValue, PurchaseErrorStatus> from the Kebechet.Types.Result package, so they are all branched on the same way:

var restoreResult = await _revenueCat.RestoreTransactions();

if (restoreResult.IsSuccess)
{
    var activeSubscriptions = restoreResult.Value?.ActiveSubscriptions;
}
else
{
    Console.WriteLine($"Restore failed: {restoreResult.Error} {restoreResult.ErrorException?.Message}");
}
Member Meaning
IsSuccess / IsError Whether the call produced a failure
Error The PurchaseErrorStatus to branch on - a closed, documented set you can switch over exhaustively
ErrorException The raw store SDK exception, for logs and diagnostics. Never surface it to users
Value The payload. Only meaningful when IsSuccess, and IsSuccess does not narrow it - null-check it

The result DTOs are thin named subclasses, so signatures stay readable and each type documents its own payload:

Result DTO Value Returned by
CanMakePaymentsResultDto bool CanMakePayments()
IntroEligibilityResultDto Dictionary<string, IntroElegibilityStatus> CheckTrialOrIntroDiscountEligibility()
OfferingsResultDto List<OfferingDto> GetOfferings()
ProductIdentifiersResultDto List<string> GetActiveSubscriptions(), GetAllPurchasedIdentifiers()
PurchaseDateResultDto DateTime? GetPurchaseDateForProductIdentifier()
ManagementUrlResultDto string? GetManagementSubscriptionUrl()
ManageSubscriptionsResultDto none (a Result<PurchaseErrorStatus>) ShowManageSubscriptions()
StorefrontResultDto string? GetStorefrontCountryCode() - empty string when the storefront is not yet known
CustomerInfoResultDto CustomerInfoDto Login(), Logout(), RestoreTransactions(), GetCustomerInfo()
PurchaseResultDto CustomerInfoDto PurchaseProduct() - carries the same payload as CustomerInfoResultDto plus a Transaction, but is a sibling of it, not a subclass

ErrorException.Message is platform-specific and may include the RevenueCat backend code and message (e.g. 7255 alias limit reached), but that is not guaranteed - branch on Error, log ErrorException.

Only PurchaseProduct() has a user-facing cancel. On every other member, PurchaseCancelledError means the CancellationToken you passed fired - not that the user cancelled anything.

Common Error Codes

Error Description
PurchaseCancelledError User cancelled the purchase
StoreProblemError Issue with the app store
NetworkError Network connectivity issue
ProductAlreadyPurchasedError Product was already purchased
PaymentPendingError Payment is pending (e.g., awaiting approval)

See PurchaseErrorStatus for the complete list.

Credits

Contributing

Issues and pull requests are welcome. See CONTRIBUTING.md for what the repository accepts and how to build and test a change.

License

This project is licensed under the MIT License.

Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  net9.0-android was computed.  net9.0-android35.0 is compatible.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-ios18.0 is compatible.  net9.0-maccatalyst was computed.  net9.0-maccatalyst18.0 is compatible.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net9.0-windows10.0.19041 is compatible.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on Kebechet.Maui.RevenueCat.InAppBilling:

Package Downloads
benxu.AppPlatform.Billing.RevenueCat

RevenueCat integration for in-app subscriptions in .NET MAUI. Provides real SDK integration for Android/iOS with optional simulator mode for testing.

SyntaxCircus.RevenueCat.Maui

Client-side RevenueCat helpers for MAUI apps: SDK initialization, identity sync on login, offering-to-DTO mapping, and a purchase/restore orchestrator built on Kebechet.Maui.RevenueCat.InAppBilling.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
10.1.0 396 11/5/2026
10.0.0 1,772 10/11/2026
9.0.0 2,344 9/1/2026
8.0.0 2,148 8/15/2026
7.4.0 1,816 8/7/2026
7.3.2 265 8/5/2026
7.3.1 217 8/5/2026
7.3.0 168 8/5/2026
7.2.0 159 8/5/2026
7.1.0 1,879 6/17/2026
7.0.1 972 6/10/2026
7.0.0 556 5/19/2026
6.0.0 449 5/13/2026
5.5.0 1,346 2/8/2026
5.4.4 1,672 1/10/2026
5.4.3 236 1/4/2026
5.4.2 769 12/7/2025
5.4.1 579 11/3/2025
5.4.0 281 11/2/2025
5.3.2 802 10/11/2025
Loading failed

ADDED
- `ShowManageSubscriptions()` shows the store's own subscription management screen: the App Store's sheet inside the app on iOS and Mac Catalyst, the subscription's Google Play page on Android. RevenueCat Android only gained it in 10.18.0, so the wrapper could not offer it on both platforms before. On Android it needs a store-managed subscription and fails with `UnsupportedError` otherwise, which includes every Test Store subscription.
- `OwnershipType.Assigned` for a purchase the user has through an organization (StoreKit's `assigned` ownership). It was reported as `OwnershipType.Unknown` before.

CHANGED
- Android uses RevenueCat Android SDK 11.1.0 (binding `Kebechet.Maui.RevenueCat.Android` 11.1.0, was 10.16.1), built on Google Play Billing Library 9.1. Apps now resolve at least `Xamarin.Android.Google.BillingClient` 9.1.0, `Xamarin.AndroidX.Core` 1.19.0, `Xamarin.AndroidX.Activity` 1.13.0, `Xamarin.Kotlin.StdLib` 2.4.0 and `Xamarin.KotlinX.Coroutines.Core` 1.11.0; an app that pins one of them lower has to raise it (NU1605).
- iOS and Mac Catalyst use RevenueCat iOS SDK 5.102.1 (binding `Kebechet.Maui.RevenueCat.iOS` 5.102.1, was 5.83.1).

NOTE
- Apart from the additions above, `IRevenueCatBilling` and its DTOs are unchanged. The RevenueCat changes in each range are in the binding release notes: Kebechet.Maui.RevenueCat.Android/11.1.0 and Kebechet.Maui.RevenueCat.iOS/5.102.1