Skip to content

Latest commit

 

History

History

README.md

maui-iap

OpenIAP for .NET MAUI - unified in-app purchases on iOS, Android, and macCatalyst from a single C# API.

Status matrix

Layer iOS Android macCatalyst
Generated types (Types.cs) yes yes yes
OpenIapClient.Instance facade and listener streams yes yes yes
StoreKit 2 / Play Billing native bindings yes yes yes
Example MAUI app yes yes yes
NuGet package shape single public package single public package single public package

Install

OpenIap.Maui 2.x requires the supported .NET 10 SDK and MAUI workload:

dotnet workload install maui
dotnet add package OpenIap.Maui

For manual .csproj edits, copy the current PackageReference from the OpenIap.Maui NuGet package page.

OpenIap.Maui is the only NuGet package apps reference. The Android and iOS binding outputs are flattened into the main NuGet package, while Google Billing, Play Services, Gson, AndroidX, and Kotlin Android libraries remain normal NuGet dependencies so apps can deduplicate them with their own package graph.

Stable NuGet releases rebuild the embedded Apple XCFramework with the current App Store-accepted toolchain (Xcode 26.6 / SDK 26.5) and verify every packaged Mach-O slice before publishing. Xcode 27 remains a source-compatibility CI lane until Apple accepts it for App Store submissions, so its guarded StoreKit 27 paths are not included in stable release artifacts yet.

Usage

using OpenIap;
using OpenIap.Maui;

var iap = OpenIapClient.Instance;
var query = (QueryResolver)iap;
var mutate = (MutationResolver)iap;

IDisposable purchaseSub = iap.PurchaseUpdated.Subscribe(async purchase =>
{
    bool verified = await VerifyOnServerAsync(purchase);
    if (!verified) return;

    await mutate.FinishTransactionAsync(
        purchase: new PurchaseInput(purchase),
        isConsumable: true);
});

IDisposable errorSub = iap.PurchaseError.Subscribe(error =>
{
    Console.WriteLine($"{error.Code}: {error.Message}");
});

await mutate.InitConnectionAsync();

var result = await query.FetchProductsAsync(new ProductRequest
{
    Skus = new[] { "premium", "coins_100" },
    Type = ProductQueryType.InApp,
});

await mutate.RequestPurchaseAsync(new RequestPurchaseProps
{
    Type = ProductQueryType.InApp,
    RequestPurchase = new RequestPurchasePropsByPlatforms
    {
        Apple = new RequestPurchaseIosProps { Sku = "coins_100", Quantity = 1 },
        Google = new RequestPurchaseAndroidProps { Skus = new[] { "coins_100" } },
    },
});

Always validate purchases on your server before granting entitlement, then call FinishTransactionAsync. On Android, unfinished purchases are refunded automatically after 3 days.

OpenIap.Maui 2.x exposes OpenIapClient; the deprecated Iap compatibility facade was removed. Replace Iap calls before upgrading from 1.x.

IAPKit API and webhooks

MAUI exposes the same kit helper surface as expo-iap and react-native-iap, with C# naming conventions:

using OpenIap;
using OpenIap.Maui;

var kit = OpenIapClient.KitApi(new KitApiOptions
{
    ApiKey = "openiap-kit_pk_<your-publishable-key>",
    BaseUrl = "https://kit.openiap.dev",
});

var status = await kit.StatusAsync("user-123");
var entitlements = await kit.EntitlementsAsync("user-123");
string? cursor = null;
do
{
    var page = await kit.ProductsAsync(new KitProductsOptions
    {
        Platform = KitProductPlatform.IOS, // required with IncludeClientPayload
        IncludeClientPayload = true,
        Limit = 25,                        // default 25, maximum 50
        Cursor = cursor,
    });
    foreach (var product in page.Products)
    {
        // Consume product.ClientPayload when present.
    }
    cursor = page.HasMore == true ? page.NextCursor : null;
} while (cursor is not null);
var payload = await kit.ClientPayloadAsync(
    "premium.monthly",
    KitProductPlatform.IOS);
await kit.BindUserAsync(purchaseToken: "token", userId: "user-123");

Publishable keys are intentionally app-readable. Never embed an openiap-kit_sk_ secret key in a shipped app; reserve it for trusted backends, CI, and MCP administration.

Client payloads are public app-readable metadata. Keep secrets and server-only rules out of them; catalog responses omit payload bodies unless explicitly requested. Payload-inclusive catalog reads require a platform and return bounded cursor pages (HasMore / NextCursor). Limit and Cursor are ignored by the legacy non-payload catalog path. If catalog churn returns INVALID_CURSOR, restart without Cursor.

IAPKit accepts store-to-server lifecycle webhooks but does not expose an outbound event stream to MAUI or other mobile SDKs. Apps should use purchase verification, scoped entitlement reads, and bounded lifecycle refreshes.

Example app

cd /path/to/openiap

# Android source runs need the native Google AAR plus the MAUI-owned module AAR.
(cd packages/google && ./gradlew :openiap:assemblePlayRelease)
(cd libraries/maui-iap/android && ../../../packages/google/gradlew :openiap:assembleRelease)

cd libraries/maui-iap/example/OpenIap.Maui.Example
adb uninstall dev.hyo.martie || true
dotnet build -t:Run -f net10.0-android

dotnet build -t:Run -f net10.0-ios
dotnet build -t:Run -f net10.0-maccatalyst

VS Code launch configurations are in libraries/maui-iap/.vscode/launch.json. The Android launcher builds both AARs before compiling the example app.

Android store variants

Source builds can select Amazon Appstore or Meta Horizon instead of Google Play. Build the matching native facade immediately before the .NET build:

cd libraries/maui-iap/android
../../../packages/google/gradlew :openiap:assembleRelease -PopenIapAndroidStore=amazon
cd ..
dotnet build example/OpenIap.Maui.Example/OpenIap.Maui.Example.csproj \
  -f net10.0-android \
  -p:OpenIapAndroidStore=amazon

Use horizon for Meta Horizon and play for Google Play. MAUI keeps each store's intermediate and output directories separate, preventing a prior store build from leaking its AAR or manifest into the next variant.

What's generated vs. hand-written

Regenerate types with:

cd specs/client && bun run generate

Links

Sponsors

Meta        Amazon Developer

Thank you to Meta and Amazon Developer for supporting OpenIAP. View sponsorship options.

OpenCollective

We also recognize sponsors and backers through OpenCollective. The original react-native-iap collective now supports the broader OpenIAP ecosystem and is managed separately from the main sponsor program.

Sponsors: OpenCollective sponsors

Backers: OpenCollective backers

Become a sponsor | Become a backer

Past supporters

Supported the project before the OpenIAP sponsor program.

Nami      Courier

License

MIT - (c) hyodotdev