Use this page with AI
Copy this into your coding assistant and add your request.
Read https://openiap.dev/docs/setup/maui and https://openiap.dev/llms.txt. Follow the reading instructions, detailed reference, and linked guides relevant to my task before making changes.
Inspect my existing project and reuse its framework and conventions. Ask me for missing product decisions. Implement the requested behavior and run the applicable checks.
Show the working result, the commands and actual test results, and any remaining limitations. Keep your explanation brief.
My request: [describe what customers should be able to do].NET MAUI Setup
maui-iap is the .NET MAUI projection of OpenIAP. Install the single NuGet package, connect to the store, fetch products, listen for purchase events, then finish transactions after your server has verified them.
For an independent store on Apple or Android, follow the community provider guide. Link its native package and select its factory in your app; purchase APIs keep the platform's existing shape and preserve the provider's store id.
Prerequisites#
| Requirement | Details |
|---|---|
| .NET | .NET 10 SDK and the MAUI workload: dotnet workload install maui |
| iOS / macCatalyst | iOS 15+ / macCatalyst 15+, Apple Developer account, matching bundle identifier, In-App Purchase capability, sandbox tester. |
| Android | Android API 24+, Google Play Developer account, matching package name, license tester, uploaded build on a test track |
| Device | Use a physical device for real purchase testing. Simulators and emulators are useful for UI checks, but store purchase support is limited. |
Installation#
Add the package to your MAUI app project:
dotnet add package OpenIap.MauiThis resolves the latest stable package from NuGet. If your project pins package versions manually, use the current NuGet package reference:
<ItemGroup>
<PackageReference Include="OpenIap.Maui" Version="3.0.0" />
</ItemGroup>Your app references a single package, OpenIap.Maui. The OpenIAP-owned iOS and Android bindings are bundled inside it, while shared dependencies (Play Services, AndroidX, Kotlin, Gson) remain ordinary NuGet dependencies so NuGet can deduplicate them with the rest of your dependency graph. The store SDK itself (Google Play Billing, the Horizon billing library, or the Amazon Appstore SDK) is linked when the app builds; see Android Store.
If you are working from this monorepo before publishing, use a project reference to the main project only. MSBuild does not propagate native references or the package's build files through ProjectReference, so the example app re-declares its native references and imports the Android store selection, pointing it at the store AARs built in packages/google. Published NuGet consumers do not need either.
<ProjectReference Include="path/to/openiap/libraries/maui-iap/src/OpenIap.Maui/OpenIap.Maui.csproj" />
<PropertyGroup>
<OpenIapGoogleAarDirectory>path/to/openiap/packages/google/openiap/build/outputs/aar/</OpenIapGoogleAarDirectory>
</PropertyGroup>
<Import Project="path/to/openiap/libraries/maui-iap/src/OpenIap.Maui/buildTransitive/OpenIap.Maui.targets" />
<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios' or $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">
<NativeReference Include="path/to/openiap/packages/apple/.build/xcframework/OpenIAP.xcframework" Kind="Framework" SmartLink="True" ForceLoad="True" />
</ItemGroup>Building the Apple library from source requires Xcode 27; the published package already embeds a prebuilt XCFramework, so NuGet consumers do not need Xcode 27 — their normal toolchain is enough.
Android Store#
A Debug build links the store of the device it deploys to — the IDE's target (AdbTarget), else the one ANDROID_SERIAL names, else the only one attached: a Quest gets Meta Horizon, a Fire device the Amazon Appstore, anything else Google Play. Only the Debug configuration looks at a device; Release and any other configuration link Google Play, so pin every build that ships to another store with OpenIapStore. The build logs its choice as openiap: store=horizon (source=device; ...).
dotnet publish -f net10.0-android -c Release -p:OpenIapStore=horizonProject Configuration#
Configure the app project once per platform before calling any store API.
Target Frameworks#
Include the platforms you ship in your MAUI app's TargetFrameworks:
<TargetFrameworks>net10.0-ios;net10.0-android;net10.0-maccatalyst</TargetFrameworks>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">15.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">15.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">24.0</SupportedOSPlatformVersion>iOS / macCatalyst#
- Set the app's bundle identifier to the same value you configured in App Store Connect.
- Enable In-App Purchase in Signing & Capabilities for the app identifier and provisioning profile.
- Sign into the device with a sandbox tester when testing App Store purchases.
Android#
- Set the Android package name to the same package configured in Play Console.
- Upload a build with the same package name and signing lineage to an internal or closed test track before testing purchases.
Usage#
The typical flow is: initialize the connection with InitConnectionAsync, fetch products with FetchProductsAsync, register the PurchaseUpdated and PurchaseError listeners, request a purchase with RequestPurchaseAsync, then finish the verified transaction with FinishTransactionAsync. The Purchase Guide covers the complete flow.
Initialize and Fetch Products#
Use OpenIapClient.Instance as the entry point. Cast it to the generated resolver interfaces when calling query or mutation APIs.
using OpenIap;
using OpenIap.Maui;
var iap = OpenIapClient.Instance;
var query = (QueryResolver)iap;
var mutate = (MutationResolver)iap;
var connected = await mutate.InitConnectionAsync();
if (!connected) throw new InvalidOperationException("Store connection failed");
var result = await query.FetchProductsAsync(new ProductRequest
{
Skus = new[] { "premium", "coins_100" },
Type = ProductQueryType.InApp,
});
var products = result is FetchProductsResultProducts { Value: { } list }
? list
: Array.Empty<Product>();Each call here has a full reference — see InitConnectionAsync and FetchProductsAsync for parameters and per-store behavior.
Listen Before Requesting a Purchase#
Purchase APIs are event-based. Register listeners before calling RequestPurchaseAsync, then finish the transaction only after server-side verification succeeds. See PurchaseUpdated, PurchaseError, and FinishTransactionAsync for parameters and per-store behavior, and Error Codes for the error.Code values.
async Task HandlePurchaseSafelyAsync(Purchase purchase)
{
try
{
bool verified = await VerifyOnServerAsync(purchase);
if (!verified) return;
await mutate.FinishTransactionAsync(
purchase: new PurchaseInput(purchase),
isConsumable: true);
GrantEntitlement(purchase.ProductId);
}
catch (Exception error)
{
Console.WriteLine(quot;Purchase processing failed: {error.Message}");
}
}
IDisposable purchaseSub = iap.PurchaseUpdated.Subscribe(
purchase => _ = HandlePurchaseSafelyAsync(purchase));
IDisposable errorSub = iap.PurchaseError.Subscribe(error =>
{
Console.WriteLine(quot;{error.Code}: {error.Message}");
});
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" },
},
},
});IAPKit API#
IAPKit is OpenIAP's hosted purchase-validation and entitlement backend (see Purchase Verification with IAPKit). The MAUI package ships the same app-facing helper as the other OpenIAP SDKs: create a kit client with your publishable key to read purchase status and entitlements and to bind a purchase to a user. Store lifecycle events (App Store Server Notifications, Google Play RTDN) are delivered to IAPKit's backend, not to your app — the client reads current state through these bounded calls rather than subscribing to a webhook stream.
using OpenIap;
using OpenIap.Maui;
var kit = OpenIapClient.KitApi(new KitApiOptions
{
ApiKey = "openiap-kit_pk_<your-publishable-key>",
BaseUrl = "https://kit.openiap.dev",
});
StatusResponse status = await kit.StatusAsync("user_123");
EntitlementsResponse entitlements = await kit.EntitlementsAsync("user_123");
BindUserResponse bind = await kit.BindUserAsync(purchase.PurchaseToken!, "user_123");Cleanup#
Dispose subscriptions and close the store connection when the page or service that owns the purchase flow is torn down. See EndConnectionAsync for its full reference.
purchaseSub.Dispose();
errorSub.Dispose();
var ended = await mutate.EndConnectionAsync();
if (!ended) Console.WriteLine("Store teardown did not complete");Example App#
The reference app lives at libraries/maui-iap/example/OpenIap.Maui.Example. It mirrors the Expo example screens: Home, All Products, In-App Purchase Flow, Subscription Flow, Available Purchases, Offer Code, Alternative Billing.
The example app builds against the in-repo Android library, so build every store's OpenIAP Android AAR once before the first run (published-package consumers skip this step):
# From the OpenIAP repo root:
(cd packages/google && ./gradlew :openiap:assemblePlayRelease :openiap:assembleHorizonRelease :openiap:assembleAmazonRelease)
(cd libraries/maui-iap/android && ../../../packages/google/gradlew :openiap:assembleRelease)Then run the example (its application id is dev.hyo.martie; uninstalling first clears stale native code):
cd libraries/maui-iap/example/OpenIap.Maui.Example
# Android device or emulator
adb uninstall dev.hyo.martie || true
dotnet build -t:Run -f net10.0-android
# iOS device or simulator
dotnet build -t:Run -f net10.0-ios
# macCatalyst
dotnet build -t:Run -f net10.0-maccatalystVS Code launch configurations are available in libraries/maui-iap/.vscode/launch.json. The iOS device launcher auto-selects a connected USB device when one is available, and the Android launcher builds the Android AARs and passes its device to the build, so the example links that device's store, before uninstalling and rebuilding the example app.
Troubleshooting#
Common failures and their causes, roughly in the order you hit them.
Products Do Not Load#
- Confirm the store product IDs exactly match the IDs passed in
ProductRequest.Skus. - Confirm the bundle identifier or Android package name matches the store app record.
- On Android, install a build from a Play test track or a locally signed build that matches the uploaded app and tester account.
- On iOS, test on a device signed with a profile that includes the In-App Purchase capability.
Android Billing Not Configured#
If Google Play shows "This version of the application is not configured for billing through Google Play", the library reached BillingClient correctly - Play itself rejected the app build. Check each of the following:
- The installed build's package name and signing key match the uploaded build.
- The build is on an internal or closed test track.
- The signed-in account is a license tester.
- The products are active in Play Console.
Android Old BillingClient Error#
If Android reports a missing enableAutoServiceReconnection method, uninstall the stale APK and rebuild with the current package. That error means an older BillingClient was still present in the installed app.
adb uninstall dev.hyo.martie || true
dotnet clean
dotnet build -t:Run -f net10.0-androidAndroid Build File Lock#
XABLD7024 usually means a previous MAUI build or deploy process still holds a generated file under obj/. Stop the running app, close duplicate dotnet build processes, then clean the example project before rebuilding.
iOS Stays on Connecting#
Verify the app is running on a signed device build with the matching bundle identifier and In-App Purchase capability. If you navigate away from a purchase screen, call EndConnectionAsync from the owning page or lifecycle service so the next screen can initialize a fresh store connection.
Next Steps#
- Purchase Guide - complete purchase flow, verification, and transaction finishing
- Subscription Guide - subscriptions, offers, renewals, and management
- Error Codes - normalized OpenIAP errors
- API Reference - generated API reference
- Store Setup - shipping beyond Apple and Google: Amazon Appstore (Fire OS and Vega OS devices) and Meta Quest (Horizon OS) support boundaries and store target configuration
- GitHub Source