From 925cfefd8d0aad5c2f9ef4f958b472edb63492d9 Mon Sep 17 00:00:00 2001
From: hyochan
Date: Tue, 16 Jun 2026 00:03:14 +0900
Subject: [PATCH 1/5] fix(maui): add net10 targets and trim android aars
Add net10 MAUI target frameworks, move Android Google dependencies to NuGet PackageReferences, and introduce OpenIapClient as the preferred facade while keeping Iap as a legacy shim.
Closes #178
Closes #179
Closes #180
---
.github/workflows/ci-maui-iap.yml | 38 ++++---
.github/workflows/release-maui.yml | 26 +++--
knowledge/_claude-context/context.md | 52 +++++++--
knowledge/internal/04-platform-packages.md | 2 +-
libraries/maui-iap/CLAUDE.md | 33 ++++--
libraries/maui-iap/CONVENTION.md | 10 +-
libraries/maui-iap/README.md | 27 +++--
.../Pages/AllProductsPage.xaml.cs | 2 +-
.../Pages/AlternativeBillingPage.xaml.cs | 14 +--
.../Pages/AvailablePurchasesPage.xaml.cs | 6 +-
.../Pages/HomePage.xaml.cs | 2 +-
.../Pages/OfferCodePage.xaml.cs | 2 +-
.../Pages/PurchaseFlowPage.xaml.cs | 18 +--
.../Pages/SubscriptionFlowPage.xaml.cs | 16 +--
.../Pages/WebhookStreamPage.xaml.cs | 2 +-
.../Utils/IapLifecycle.cs | 4 +-
libraries/maui-iap/src/Directory.Build.props | 17 ++-
.../OpenIap.Maui.Bindings.Android.csproj | 33 ++----
.../OpenIap.Maui.Bindings.iOS.csproj | 2 +-
.../src/OpenIap.Maui/OpenIap.Maui.csproj | 36 +++---
.../maui-iap/src/OpenIap.Maui/OpenIap.cs | 47 ++++++--
llms-full.txt | 35 +++---
llms.txt | 24 ++--
packages/docs/public/llms-full.txt | 35 +++---
packages/docs/public/llms.txt | 24 ++--
packages/docs/scripts/replace-csharp-tabs.mjs | 4 +-
.../android/acknowledge-purchase-android.tsx | 2 +-
...ternative-billing-availability-android.tsx | 2 +-
.../apis/android/consume-purchase-android.tsx | 2 +-
...eate-alternative-billing-token-android.tsx | 2 +-
...ling-program-reporting-details-android.tsx | 2 +-
.../enable-billing-program-android.tsx | 2 +-
.../is-billing-program-available-android.tsx | 2 +-
.../android/launch-external-link-android.tsx | 2 +-
...how-alternative-billing-dialog-android.tsx | 2 +-
.../docs/apis/deep-link-to-subscriptions.tsx | 2 +-
.../src/pages/docs/apis/end-connection.tsx | 2 +-
.../src/pages/docs/apis/fetch-products.tsx | 2 +-
.../pages/docs/apis/finish-transaction.tsx | 2 +-
.../docs/apis/get-active-subscriptions.tsx | 2 +-
.../docs/apis/get-available-purchases.tsx | 2 +-
.../src/pages/docs/apis/get-storefront.tsx | 2 +-
.../docs/apis/has-active-subscriptions.tsx | 2 +-
.../src/pages/docs/apis/init-connection.tsx | 4 +-
.../apis/ios/begin-refund-request-ios.tsx | 2 +-
...n-present-external-purchase-notice-ios.tsx | 2 +-
.../docs/apis/ios/clear-transaction-ios.tsx | 2 +-
.../docs/apis/ios/current-entitlement-ios.tsx | 2 +-
.../apis/ios/get-all-transactions-ios.tsx | 2 +-
.../docs/apis/ios/get-app-transaction-ios.tsx | 2 +-
...xternal-purchase-custom-link-token-ios.tsx | 2 +-
.../apis/ios/get-pending-transactions-ios.tsx | 2 +-
.../apis/ios/get-promoted-product-ios.tsx | 2 +-
.../docs/apis/ios/get-receipt-data-ios.tsx | 2 +-
.../docs/apis/ios/get-storefront-ios.tsx | 4 +-
.../docs/apis/ios/get-transaction-jws-ios.tsx | 2 +-
...-for-external-purchase-custom-link-ios.tsx | 2 +-
.../ios/is-eligible-for-intro-offer-ios.tsx | 2 +-
.../apis/ios/is-transaction-verified-ios.tsx | 2 +-
.../docs/apis/ios/latest-transaction-ios.tsx | 2 +-
.../ios/present-code-redemption-sheet-ios.tsx | 2 +-
.../present-external-purchase-link-ios.tsx | 2 +-
...ent-external-purchase-notice-sheet-ios.tsx | 2 +-
...quest-purchase-on-promoted-product-ios.tsx | 2 +-
...ternal-purchase-custom-link-notice-ios.tsx | 2 +-
.../ios/show-manage-subscriptions-ios.tsx | 2 +-
.../docs/apis/ios/subscription-status-ios.tsx | 2 +-
.../docs/src/pages/docs/apis/ios/sync-ios.tsx | 2 +-
.../docs/apis/ios/validate-receipt-ios.tsx | 2 +-
.../src/pages/docs/apis/request-purchase.tsx | 10 +-
.../src/pages/docs/apis/restore-purchases.tsx | 2 +-
...oper-provided-billing-listener-android.tsx | 4 +-
.../user-choice-billing-listener-android.tsx | 2 +-
.../ios/promoted-product-listener-ios.tsx | 2 +-
.../docs/events/purchase-error-listener.tsx | 6 +-
.../docs/events/purchase-updated-listener.tsx | 8 +-
.../subscription-billing-issue-listener.tsx | 4 +-
.../docs/src/pages/docs/features/discount.tsx | 6 +-
.../docs/src/pages/docs/features/refund.tsx | 2 +-
.../features/subscription-billing-issue.tsx | 2 +-
.../docs/features/subscription/index.tsx | 28 ++---
.../subscription/upgrade-downgrade.tsx | 26 ++---
.../src/pages/docs/features/validation.tsx | 2 +-
.../docs/src/pages/docs/getting-started.tsx | 6 +-
packages/docs/src/pages/docs/kit-backend.tsx | 2 +-
packages/docs/src/pages/docs/setup/maui.tsx | 33 ++++--
.../docs/types/alternative-billing-types.tsx | 26 ++---
.../src/pages/docs/types/billing-programs.tsx | 8 +-
.../docs/types/external-purchase-link.tsx | 6 +-
.../docs/types/ios/app-transaction-ios.tsx | 2 +-
.../src/pages/docs/types/product-request.tsx | 6 +-
.../purchase-updated-listener-options.tsx | 2 +-
.../docs/types/request-purchase-props.tsx | 4 +-
.../verify-purchase-with-provider-result.tsx | 2 +-
packages/docs/src/pages/introduction.tsx | 2 +-
scripts/agent/compile-context.ts | 31 +++---
scripts/audit-non-godot-parity.mjs | 104 +++++++++++++++---
scripts/sync-versions.sh | 26 ++---
98 files changed, 575 insertions(+), 390 deletions(-)
diff --git a/.github/workflows/ci-maui-iap.yml b/.github/workflows/ci-maui-iap.yml
index a48ce58a..f40006fb 100644
--- a/.github/workflows/ci-maui-iap.yml
+++ b/.github/workflows/ci-maui-iap.yml
@@ -41,7 +41,7 @@ env:
jobs:
compile-check:
- name: Compile Check (net9.0 shared)
+ name: Compile Check (net9.0 / net10.0 shared)
runs-on: ubuntu-latest
timeout-minutes: 15
defaults:
@@ -52,28 +52,30 @@ jobs:
with:
fetch-depth: 1
- - name: Setup .NET 9 SDK
+ - name: Setup .NET 10 SDK
uses: actions/setup-dotnet@v5
with:
- dotnet-version: '9.0.x'
+ dotnet-version: "10.0.x"
- # The shared `net9.0` build is the fast lane — no AAR / xcframework, no
+ # The shared `net9.0` / `net10.0` builds are the fast lane — no AAR / xcframework, no
# MAUI workload, no native binding csprojs in the project graph. Two
# things make this work:
# 1. `` is conditional on having a platform identifier in
- # OpenIap.Maui.csproj, so the net9.0 TFM doesn't activate MAUI.
+ # OpenIap.Maui.csproj, so the shared TFMs don't activate MAUI.
# 2. `-p:TargetFrameworks=net9.0` (PLURAL — overrides the project's
# `` list to a single TFM). This must be plural,
# not `-p:TargetFramework=net9.0` (singular) or `-f net9.0`: those
# filter the inner BUILD but leave the implicit RESTORE walking
- # all 4 TFMs in the multi-target list, which loads the conditional
+ # all platform TFMs in the multi-target list, which loads the conditional
# ProjectReferences to Bindings.Android / Bindings.iOS for
# restore-time evaluation and triggers NETSDK1147 / NETSDK1178.
- - name: Build library (net9.0)
- run: dotnet build src/OpenIap.Maui/OpenIap.Maui.csproj -p:TargetFrameworks=net9.0 --nologo
+ - name: Build library (shared)
+ run: |
+ dotnet build src/OpenIap.Maui/OpenIap.Maui.csproj -p:TargetFrameworks=net9.0 --nologo
+ dotnet build src/OpenIap.Maui/OpenIap.Maui.csproj -p:TargetFrameworks=net10.0 --nologo
android-binding:
- name: Android binding (net9.0-android)
+ name: Android binding (net9.0-android + net10.0-android)
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
@@ -87,10 +89,10 @@ jobs:
distribution: 'temurin'
java-version: '17'
- - name: Setup .NET 9 SDK
+ - name: Setup .NET 10 SDK
uses: actions/setup-dotnet@v5
with:
- dotnet-version: '9.0.x'
+ dotnet-version: "10.0.x"
- name: Install MAUI workload
run: dotnet workload install maui-android --skip-sign-check
@@ -115,11 +117,13 @@ jobs:
- name: Build Android binding + library
working-directory: libraries/maui-iap
run: |
- dotnet build src/OpenIap.Maui.Bindings.Android/OpenIap.Maui.Bindings.Android.csproj --nologo
+ dotnet build src/OpenIap.Maui.Bindings.Android/OpenIap.Maui.Bindings.Android.csproj -p:TargetFrameworks=net9.0-android --nologo
+ dotnet build src/OpenIap.Maui.Bindings.Android/OpenIap.Maui.Bindings.Android.csproj -p:TargetFrameworks=net10.0-android --nologo
dotnet build src/OpenIap.Maui/OpenIap.Maui.csproj -p:TargetFrameworks=net9.0-android --nologo
+ dotnet build src/OpenIap.Maui/OpenIap.Maui.csproj -p:TargetFrameworks=net10.0-android --nologo
ios-binding:
- name: iOS binding (net9.0-ios + maccatalyst)
+ name: iOS binding (net9.0/net10.0 ios + maccatalyst)
runs-on: macos-15
timeout-minutes: 45
steps:
@@ -132,10 +136,10 @@ jobs:
with:
xcode-version: ${{ env.XCODE_VERSION }}
- - name: Setup .NET 9 SDK
+ - name: Setup .NET 10 SDK
uses: actions/setup-dotnet@v5
with:
- dotnet-version: '9.0.x'
+ dotnet-version: "10.0.x"
- name: Install MAUI workload
run: dotnet workload install maui --skip-sign-check
@@ -155,10 +159,14 @@ jobs:
working-directory: libraries/maui-iap/src/OpenIap.Maui.Bindings.iOS
run: |
dotnet build -p:TargetFrameworks=net9.0-ios --nologo
+ dotnet build -p:TargetFrameworks=net10.0-ios --nologo
dotnet build -p:TargetFrameworks=net9.0-maccatalyst --nologo
+ dotnet build -p:TargetFrameworks=net10.0-maccatalyst --nologo
- name: Build library (ios + maccatalyst)
working-directory: libraries/maui-iap/src/OpenIap.Maui
run: |
dotnet build -p:TargetFrameworks=net9.0-ios --nologo
+ dotnet build -p:TargetFrameworks=net10.0-ios --nologo
dotnet build -p:TargetFrameworks=net9.0-maccatalyst --nologo
+ dotnet build -p:TargetFrameworks=net10.0-maccatalyst --nologo
diff --git a/.github/workflows/release-maui.yml b/.github/workflows/release-maui.yml
index acb53943..42388ab0 100644
--- a/.github/workflows/release-maui.yml
+++ b/.github/workflows/release-maui.yml
@@ -34,28 +34,30 @@ env:
jobs:
validate:
- name: Validate (net9.0 shared)
+ name: Validate (net9.0 / net10.0 shared)
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v6
- - name: Setup .NET 9 SDK
+ - name: Setup .NET 10 SDK
uses: actions/setup-dotnet@v5
with:
- dotnet-version: "9.0.x"
+ dotnet-version: "10.0.x"
- # The shared `net9.0` build catches Types.cs / public-API regressions
+ # The shared `net9.0` / `net10.0` builds catch Types.cs / public-API regressions
# without needing the MAUI / android / ios workloads. Two things make
# this work — see ci-maui-iap.yml for the same pattern with details.
# Note: `-p:TargetFrameworks=...` is PLURAL (overrides the project's
# multi-target list); the singular `-p:TargetFramework=...` and `-f`
# leave the implicit restore walking all TFMs.
- - name: Build library (net9.0)
- run: dotnet build libraries/maui-iap/src/OpenIap.Maui/OpenIap.Maui.csproj -p:TargetFrameworks=net9.0
+ - name: Build library (shared)
+ run: |
+ dotnet build libraries/maui-iap/src/OpenIap.Maui/OpenIap.Maui.csproj -p:TargetFrameworks=net9.0
+ dotnet build libraries/maui-iap/src/OpenIap.Maui/OpenIap.Maui.csproj -p:TargetFrameworks=net10.0
validate-multitarget:
- name: Validate (net9.0-android / net9.0-ios / net9.0-maccatalyst)
+ name: Validate (net9.0/net10.0 platform TFMs)
runs-on: macos-15
timeout-minutes: 60
steps:
@@ -72,10 +74,10 @@ jobs:
distribution: "temurin"
java-version: "17"
- - name: Setup .NET 9 SDK
+ - name: Setup .NET 10 SDK
uses: actions/setup-dotnet@v5
with:
- dotnet-version: "9.0.x"
+ dotnet-version: "10.0.x"
- name: Install MAUI workload
run: dotnet workload install maui --skip-sign-check
@@ -95,6 +97,8 @@ jobs:
working-directory: libraries/maui-iap/android
run: ../../../packages/google/gradlew :openiap:assembleRelease
+ # OpenIap.Maui.csproj includes net9.0-android, net10.0-android,
+ # net9.0-ios, net10.0-ios, net9.0-maccatalyst, and net10.0-maccatalyst.
- name: Build all target frameworks
run: dotnet build libraries/maui-iap/src/OpenIap.Maui/OpenIap.Maui.csproj
@@ -118,10 +122,10 @@ jobs:
distribution: "temurin"
java-version: "17"
- - name: Setup .NET 9 SDK
+ - name: Setup .NET 10 SDK
uses: actions/setup-dotnet@v5
with:
- dotnet-version: "9.0.x"
+ dotnet-version: "10.0.x"
- name: Install MAUI workload
run: dotnet workload install maui --skip-sign-check
diff --git a/knowledge/_claude-context/context.md b/knowledge/_claude-context/context.md
index 9b62d064..6a85902b 100644
--- a/knowledge/_claude-context/context.md
+++ b/knowledge/_claude-context/context.md
@@ -1,7 +1,7 @@
# OpenIAP Project Context
> **Auto-generated for Claude Code**
-> Last updated: 2026-05-16T12:59:43.317Z
+> Last updated: 2026-06-15T14:57:16.333Z
>
> Usage: `claude --context knowledge/_claude-context/context.md`
@@ -861,6 +861,12 @@ The mechanical guardrail for this checklist is:
bun run audit:parity
```
+This mirrors CI's **Audit SDK Parity** job and is intentionally run by the
+pre-commit hook on every commit. Do not bypass it for docs/version-only changes:
+the audit also checks generated docs version metadata and the Godot Android
+GDAP dependency pin against `openiap-versions.json`, so release-version drift can
+break CI even when no SDK source code changed.
+
This audit treats `libraries/expo-iap/example` as the non-Godot example SSOT
and fails when:
@@ -871,11 +877,17 @@ and fails when:
- a GraphQL Query/Mutation/Subscription operation is added or removed without
updating the operation parity registry
- generated types or shared TS runtime helpers drift from `packages/gql`
-
-Run it after type generation and before opening a PR for SDK/API/example
-changes. If it fails for a newly introduced operation or feature, update the
-missing SDK bridge/example/test coverage first, then update the parity registry
-in [`scripts/audit-non-godot-parity.mjs`](../../scripts/audit-non-godot-parity.mjs).
+- framework/package version metadata or Godot Android GDAP dependencies drift
+ from the package/version SSOTs
+
+Run it after type generation, after version syncs, and before opening a PR for
+SDK/API/example/docs-version changes. If it fails for a newly introduced
+operation or feature, update the missing SDK bridge/example/test coverage first,
+then update the parity registry in
+[`scripts/audit-non-godot-parity.mjs`](../../scripts/audit-non-godot-parity.mjs).
+If it fails for Godot GDAP dependency drift, run
+`./libraries/godot-iap/scripts/write-gdap.sh` and commit the regenerated
+`libraries/godot-iap/addons/godot-iap/android/GodotIap.gdap`.
### The bug pattern
@@ -900,7 +912,7 @@ For every new/changed handler in the generated types, verify **all five** of the
| **flutter_inapp_purchase** | `lib/types.dart` (generated) | getter on `FlutterInappPurchase` in `lib/flutter_inapp_purchase.dart` | `case "":` in `ios/Classes/FlutterInappPurchasePlugin.swift`, Android plugin `onMethodCall` | `queryHandlers` / `mutationHandlers` / `subscriptionHandlers` bundles near the bottom of `flutter_inapp_purchase.dart` | Mock + test in `test/ios_methods_test.dart` (and the `errors_unit_test.dart` error-mapping test) |
| **kmp-iap** | `library/src/commonMain/.../openiap/Types.kt` (generated interface) | exposed via `KmpInAppPurchase` / `kmpIapInstance` | `library/src/iosMain/.../InAppPurchaseIOS.kt` — must call `openIapModule.WithCompletion { ... }`, **never** `throw UnsupportedOperationException` | Not required (interface dispatch) | `library/src/commonTest/` if testable cross-platform |
| **godot-iap** | `addons/godot-iap/types.gd` (generated) | public `snake_case` function in `addons/godot-iap/godot_iap.gd` | `ios-gdextension/Sources/GodotIap/GodotIap.swift` (iOS), `android/src/main/java/.../GodotIap.java` (Android) | Not required | Manual testing — no automated test suite yet |
-| **maui-iap** | `src/OpenIap.Maui/Types.cs` (generated) | `OpenIap.QueryResolver` / `MutationResolver` interfaces in `Types.cs`; `IOpenIap` adds the listener-stream contract; static facade is `OpenIap.Maui.Iap`; IAPKit helpers mirror TypeScript via `Iap.KitApi(...)`, `Iap.ConnectWebhookStream(...)`, `Iap.ParseWebhookEventData(...)`, and `Iap.WebhookEventTypes` | Android: `OpenIapMauiModule.kt` in `libraries/maui-iap/android/openiap/` (JSON-shaped Java facade over `packages/google`), bound by `OpenIap.Maui.Bindings.Android.csproj`, consumed by `Platforms/Android/OpenIapAndroid.cs`. iOS / macCatalyst: existing `OpenIapModule+ObjC.swift` bridge in `packages/apple`, bound by hand-written `OpenIap.Maui.Bindings.iOS/ApiDefinition.cs`, consumed by `Platforms/iOS/OpenIapIOS.cs` (+ subclass `OpenIapMacCatalyst`). | Not required (interface dispatch) | Example app `libraries/maui-iap/example/OpenIap.Maui.Example` builds for net9.0-android / net9.0-ios / net9.0-maccatalyst (manual device testing for purchase flow); no xUnit tests yet |
+| **maui-iap** | `src/OpenIap.Maui/Types.cs` (generated) | `OpenIap.QueryResolver` / `MutationResolver` interfaces in `Types.cs`; `IOpenIap` adds the listener-stream contract; static facade is `OpenIap.Maui.OpenIapClient` (`OpenIap.Maui.Iap` remains as a legacy shim); IAPKit helpers mirror TypeScript via `OpenIapClient.KitApi(...)`, `OpenIapClient.ConnectWebhookStream(...)`, `OpenIapClient.ParseWebhookEventData(...)`, and `OpenIapClient.WebhookEventTypes` | Android: `OpenIapMauiModule.kt` in `libraries/maui-iap/android/openiap/` (JSON-shaped Java facade over `packages/google`), bound by `OpenIap.Maui.Bindings.Android.csproj`, consumed by `Platforms/Android/OpenIapAndroid.cs`. Google Billing / Play Services / Gson / AndroidX / Kotlin dependencies must stay NuGet `PackageReference`s, not fat-bundled AARs. iOS / macCatalyst: existing `OpenIapModule+ObjC.swift` bridge in `packages/apple`, bound by hand-written `OpenIap.Maui.Bindings.iOS/ApiDefinition.cs`, consumed by `Platforms/iOS/OpenIapIOS.cs` (+ subclass `OpenIapMacCatalyst`). | Not required (interface dispatch) | Example app `libraries/maui-iap/example/OpenIap.Maui.Example` builds for net9.0-android / net9.0-ios / net9.0-maccatalyst; package CI builds net9/net10 shared, Android, iOS, and macCatalyst TFMs (manual device testing for purchase flow); no xUnit tests yet |
### Platform suffix rule (who needs what)
@@ -1864,6 +1876,7 @@ react-native-iap / godot-iap, then the Apple wrapper must also default to
description is the canonical statement.
When changing a default, update:
+
1. The GraphQL schema description.
2. Re-run `bun run generate`.
3. Every wrapper SDK's `?? ` expression and JSDoc / KDoc / etc.
@@ -1878,6 +1891,7 @@ The audit script greps for fields that don't appear in the type definition
and flags them.
Example failure modes already encountered:
+
- `BillingProgramAvailabilityResultAndroid` doc listed
`responseCode` + `debugMessage` — neither field exists; the type has
`billingProgram` + `isAvailable`.
@@ -1903,6 +1917,7 @@ the union is `'browser'` only, but the doc claimed
Anchor links should point to existing pages and section anchors. Common
recent failures:
+
- "Use verifyPurchase" link pointed to `/docs/apis/get-active-subscriptions`
(totally unrelated).
- `getExternalPurchaseCustomLinkTokenIOS` Returns linked to the
@@ -1930,6 +1945,7 @@ exactly as Google / Apple states it.
Code examples in doc pages should at minimum parse / type-check against
the wrapper they target. The audit script does NOT yet run a full
TypeScript / Kotlin / Dart parser, but it does:
+
- Verify imports (`import {…} from 'expo-iap'`) reference symbols that
expo-iap actually exports.
- Verify field accesses on shown objects (e.g. `purchase.purchaseToken`)
@@ -1961,6 +1977,27 @@ the GitHub Release does not exist yet.
`bun run audit:docs` fails bare package/version entries under published
`Package Releases` blocks so link regressions are caught before publishing.
+### R10 — Docs version metadata stays synced with package metadata
+
+`packages/docs/src/lib/versioning.ts` must not import package metadata from
+outside `packages/docs`. Vercel uploads the docs package root, so imports such
+as `../../../../libraries/expo-iap/package.json?raw` pass locally but fail in
+Vercel builds.
+
+Framework package versions and Android SDK constants used by docs must flow
+through `packages/docs/src/generated/version-metadata.json`, which is generated
+by `scripts/sync-versions.sh` from the real SSOT files:
+
+- Expo / React Native: each library `package.json`
+- Flutter: `libraries/flutter_inapp_purchase/pubspec.yaml`
+- Godot: `libraries/godot-iap/addons/godot-iap/plugin.cfg`
+- KMP: `libraries/kmp-iap/gradle.properties` and `gradle/libs.versions.toml`
+- MAUI: `libraries/maui-iap/src/OpenIap.Maui/OpenIap.Maui.csproj`
+- Google Android SDK / Play Billing: `packages/google/openiap/build.gradle.kts`
+
+`bun run audit:docs` fails if this generated metadata drifts from the SSOT
+files or if `versioning.ts` reintroduces raw imports outside `packages/docs`.
+
## Pre-commit checklist
Run before every `git push` on docs / SDK changes:
@@ -1990,6 +2027,7 @@ positives in CI.
`scripts/audit-docs.ts` is the executable companion to this guide. It
parses every `/docs/apis/*.tsx` and `/docs/types/*.tsx` page, extracts:
+
- `` targets
- `fieldName` mentions inside Returns / Parameters tables
- String-literal enum values in `'…'` blocks
diff --git a/knowledge/internal/04-platform-packages.md b/knowledge/internal/04-platform-packages.md
index d814869e..afc3d660 100644
--- a/knowledge/internal/04-platform-packages.md
+++ b/knowledge/internal/04-platform-packages.md
@@ -174,7 +174,7 @@ For every new/changed handler in the generated types, verify **all five** of the
| **flutter_inapp_purchase** | `lib/types.dart` (generated) | getter on `FlutterInappPurchase` in `lib/flutter_inapp_purchase.dart` | `case "":` in `ios/Classes/FlutterInappPurchasePlugin.swift`, Android plugin `onMethodCall` | `queryHandlers` / `mutationHandlers` / `subscriptionHandlers` bundles near the bottom of `flutter_inapp_purchase.dart` | Mock + test in `test/ios_methods_test.dart` (and the `errors_unit_test.dart` error-mapping test) |
| **kmp-iap** | `library/src/commonMain/.../openiap/Types.kt` (generated interface) | exposed via `KmpInAppPurchase` / `kmpIapInstance` | `library/src/iosMain/.../InAppPurchaseIOS.kt` — must call `openIapModule.WithCompletion { ... }`, **never** `throw UnsupportedOperationException` | Not required (interface dispatch) | `library/src/commonTest/` if testable cross-platform |
| **godot-iap** | `addons/godot-iap/types.gd` (generated) | public `snake_case` function in `addons/godot-iap/godot_iap.gd` | `ios-gdextension/Sources/GodotIap/GodotIap.swift` (iOS), `android/src/main/java/.../GodotIap.java` (Android) | Not required | Manual testing — no automated test suite yet |
-| **maui-iap** | `src/OpenIap.Maui/Types.cs` (generated) | `OpenIap.QueryResolver` / `MutationResolver` interfaces in `Types.cs`; `IOpenIap` adds the listener-stream contract; static facade is `OpenIap.Maui.Iap`; IAPKit helpers mirror TypeScript via `Iap.KitApi(...)`, `Iap.ConnectWebhookStream(...)`, `Iap.ParseWebhookEventData(...)`, and `Iap.WebhookEventTypes` | Android: `OpenIapMauiModule.kt` in `libraries/maui-iap/android/openiap/` (JSON-shaped Java facade over `packages/google`), bound by `OpenIap.Maui.Bindings.Android.csproj`, consumed by `Platforms/Android/OpenIapAndroid.cs`. iOS / macCatalyst: existing `OpenIapModule+ObjC.swift` bridge in `packages/apple`, bound by hand-written `OpenIap.Maui.Bindings.iOS/ApiDefinition.cs`, consumed by `Platforms/iOS/OpenIapIOS.cs` (+ subclass `OpenIapMacCatalyst`). | Not required (interface dispatch) | Example app `libraries/maui-iap/example/OpenIap.Maui.Example` builds for net9.0-android / net9.0-ios / net9.0-maccatalyst (manual device testing for purchase flow); no xUnit tests yet |
+| **maui-iap** | `src/OpenIap.Maui/Types.cs` (generated) | `OpenIap.QueryResolver` / `MutationResolver` interfaces in `Types.cs`; `IOpenIap` adds the listener-stream contract; static facade is `OpenIap.Maui.OpenIapClient` (`OpenIap.Maui.Iap` remains as a legacy shim); IAPKit helpers mirror TypeScript via `OpenIapClient.KitApi(...)`, `OpenIapClient.ConnectWebhookStream(...)`, `OpenIapClient.ParseWebhookEventData(...)`, and `OpenIapClient.WebhookEventTypes` | Android: `OpenIapMauiModule.kt` in `libraries/maui-iap/android/openiap/` (JSON-shaped Java facade over `packages/google`), bound by `OpenIap.Maui.Bindings.Android.csproj`, consumed by `Platforms/Android/OpenIapAndroid.cs`. Google Billing / Play Services / Gson / AndroidX / Kotlin dependencies must stay NuGet `PackageReference`s, not fat-bundled AARs. iOS / macCatalyst: existing `OpenIapModule+ObjC.swift` bridge in `packages/apple`, bound by hand-written `OpenIap.Maui.Bindings.iOS/ApiDefinition.cs`, consumed by `Platforms/iOS/OpenIapIOS.cs` (+ subclass `OpenIapMacCatalyst`). | Not required (interface dispatch) | Example app `libraries/maui-iap/example/OpenIap.Maui.Example` builds for net9.0-android / net9.0-ios / net9.0-maccatalyst; package CI builds net9/net10 shared, Android, iOS, and macCatalyst TFMs (manual device testing for purchase flow); no xUnit tests yet |
### Platform suffix rule (who needs what)
diff --git a/libraries/maui-iap/CLAUDE.md b/libraries/maui-iap/CLAUDE.md
index 95f8d90c..ff40250d 100644
--- a/libraries/maui-iap/CLAUDE.md
+++ b/libraries/maui-iap/CLAUDE.md
@@ -7,8 +7,11 @@ imports the generated [`Types.cs`](src/OpenIap.Maui/Types.cs) from
(`IOpenIap`), and delegates the actual purchase work to the OpenIAP
native packages — `packages/apple` on iOS / macCatalyst, `packages/google`
on Android. It also exposes the same IAPKit HTTP/webhook helper surface as the
-TypeScript SDKs through `Iap.KitApi(...)`, `Iap.ConnectWebhookStream(...)`, and
-`Iap.ParseWebhookEventData(...)`.
+TypeScript SDKs through `OpenIapClient.KitApi(...)`,
+`OpenIapClient.ConnectWebhookStream(...)`, and
+`OpenIapClient.ParseWebhookEventData(...)`. The legacy `Iap` facade remains as
+a compatibility shim, but new code should use `OpenIapClient` to avoid
+namespace/type collisions with app namespaces such as `OpenIap.Maui.Iap`.
## Required pre-work
@@ -21,8 +24,9 @@ Before editing anything in this library:
[`04-platform-packages.md`](../../knowledge/internal/04-platform-packages.md#sdk-parity-checklist-critical--prevents-declared-but-not-implemented).
2. Read [`CONVENTION.md`](./CONVENTION.md) for C# / MAUI-specific naming
and style rules.
-3. Run `dotnet build src/OpenIap.Maui/OpenIap.Maui.csproj -f net9.0` (the
- shared TFM compiles without the MAUI workload) before pushing.
+3. Run `dotnet build src/OpenIap.Maui/OpenIap.Maui.csproj -p:TargetFrameworks=net9.0`
+ and `dotnet build src/OpenIap.Maui/OpenIap.Maui.csproj -p:TargetFrameworks=net10.0`
+ (the shared TFMs compile without the MAUI workload) before pushing.
## Project layout
@@ -34,8 +38,8 @@ libraries/maui-iap/
├── openiap-versions.json — symlink for native spec/apple/google versions
└── src/
└── OpenIap.Maui/
- ├── OpenIap.Maui.csproj — multi-target (net9.0 + ios/android/maccatalyst)
- ├── OpenIap.cs — IOpenIap contract + static facade
+ ├── OpenIap.Maui.csproj — multi-target (net9.0/net10.0 + ios/android/maccatalyst)
+ ├── OpenIap.cs — IOpenIap contract + static facades
├── UnsupportedOpenIap.cs — fallback for non-platform builds
├── Types.cs — AUTO-GENERATED, do not edit
└── Platforms/
@@ -138,9 +142,13 @@ so the main package flattens their outputs instead of declaring unpublished
The package includes:
- binding DLLs in `lib//`
-- Android AARs in `lib/net9.0-android35.0/`, including the binding support AAR
- with Maven-resolved jars, the MAUI-owned module AAR, and the unbound
- `openiap-play-release.aar` runtime dependency
+- Android AARs in `lib/net9.0-android35.0/` and
+ `lib/net10.0-android36.0/`, limited to OpenIAP-owned artifacts: the
+ MAUI-owned module AAR and the unbound `openiap-play-release.aar` runtime
+ dependency
+- Android Google Billing, Play Services, Gson, AndroidX, and Kotlin runtime
+ libraries as normal NuGet `PackageReference` dependencies, not embedded AAR
+ copies
- iOS / macCatalyst `OpenIap.Maui.Bindings.iOS.resources.zip` sidecars
next to the iOS binding DLLs
@@ -180,7 +188,8 @@ For maui-iap specifically:
```bash
# Cross-platform shared compile (no MAUI workload required)
-dotnet build src/OpenIap.Maui/OpenIap.Maui.csproj -f net9.0
+dotnet build src/OpenIap.Maui/OpenIap.Maui.csproj -p:TargetFrameworks=net9.0
+dotnet build src/OpenIap.Maui/OpenIap.Maui.csproj -p:TargetFrameworks=net10.0
# Full multi-target build (requires MAUI workload)
dotnet workload install maui
@@ -192,7 +201,9 @@ dotnet build src/OpenIap.Maui/OpenIap.Maui.csproj
1. Regenerate types if `packages/gql/src/*.graphql` changed:
`cd packages/gql && bun run generate`
2. Run `bash scripts/sync-versions.sh` from repo root.
-3. Run `dotnet build src/OpenIap.Maui/OpenIap.Maui.csproj -f net9.0`.
+3. Run the shared compile checks:
+ `dotnet build src/OpenIap.Maui/OpenIap.Maui.csproj -p:TargetFrameworks=net9.0`
+ and `dotnet build src/OpenIap.Maui/OpenIap.Maui.csproj -p:TargetFrameworks=net10.0`.
4. Verify `Types.cs` matches `packages/gql/src/generated/Types.cs`
byte-for-byte (the sync should keep them in lockstep).
diff --git a/libraries/maui-iap/CONVENTION.md b/libraries/maui-iap/CONVENTION.md
index d1a576c5..542ab1c0 100644
--- a/libraries/maui-iap/CONVENTION.md
+++ b/libraries/maui-iap/CONVENTION.md
@@ -8,17 +8,17 @@ C# / .NET MAUI specifics on top of the monorepo-wide rules in
- **C# 12** with `enable` and
`true` (configured in
the .csproj).
-- **.NET 8** target with platform-specific TFMs:
- `net9.0;net9.0-android;net9.0-ios;net9.0-maccatalyst`.
-- The shared `net9.0` TFM compiles without the MAUI workload — keep it
- green for fast PR-time validation.
+- **.NET 9 / .NET 10** targets with platform-specific TFMs:
+ `net9.0;net10.0;net9.0-android;net10.0-android;net9.0-ios;net10.0-ios;net9.0-maccatalyst;net10.0-maccatalyst`.
+- The shared `net9.0` and `net10.0` TFMs compile without the MAUI workload —
+ keep both green for fast PR-time validation.
## Namespaces
| Namespace | Owns |
| ------------------------------------ | ---------------------------------------------- |
| `OpenIap` | Generated types, enums, resolver interfaces. |
-| `OpenIap.Maui` | `IOpenIap` contract, static `Iap` facade. |
+| `OpenIap.Maui` | `IOpenIap` contract, static `OpenIapClient` facade, legacy `Iap` shim. |
| `OpenIap.Maui.Platforms.Android` | Android bridge implementation. |
| `OpenIap.Maui.Platforms.iOS` | iOS bridge implementation. |
| `OpenIap.Maui.Platforms.MacCatalyst` | macCatalyst bridge implementation. |
diff --git a/libraries/maui-iap/README.md b/libraries/maui-iap/README.md
index 8864377e..9e3b1e80 100644
--- a/libraries/maui-iap/README.md
+++ b/libraries/maui-iap/README.md
@@ -8,14 +8,14 @@ macCatalyst from a single C# API.
| Layer | iOS | Android | macCatalyst |
| ------------------------------------------ | :-------------------: | :-------------------: | :-------------------: |
| Generated types (`Types.cs`) | yes | yes | yes |
-| `Iap.Instance` facade and listener streams | 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
-Requires .NET 9 SDK and the MAUI workload:
+Requires the .NET 9 or .NET 10 SDK and the MAUI workload:
```bash
dotnet workload install maui
@@ -25,9 +25,11 @@ dotnet add package OpenIap.Maui
For manual `.csproj` edits, copy the current PackageReference from the
[OpenIap.Maui NuGet package page](https://www.nuget.org/packages/OpenIap.Maui).
-`OpenIap.Maui` is the only NuGet package apps reference. The Android binding,
-iOS binding, Google Play Billing AARs, and StoreKit xcframework resources are
-flattened into the main NuGet package.
+`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.
## Usage
@@ -35,7 +37,7 @@ flattened into the main NuGet package.
using OpenIap;
using OpenIap.Maui;
-var iap = Iap.Instance;
+var iap = OpenIapClient.Instance;
var query = (QueryResolver)iap;
var mutate = (MutationResolver)iap;
@@ -77,6 +79,10 @@ Always validate purchases on your server before granting entitlement, then call
`FinishTransactionAsync`. On Android, unfinished purchases are refunded
automatically after 3 days.
+`Iap` remains available as a backward-compatible facade, but new code should use
+`OpenIapClient` so app namespaces such as `OpenIap.Maui.Iap` do not collide
+with the facade type name.
+
## IAPKit API and webhooks
MAUI exposes the same kit helper surface as `expo-iap` and
@@ -86,7 +92,7 @@ MAUI exposes the same kit helper surface as `expo-iap` and
using OpenIap;
using OpenIap.Maui;
-var kit = Iap.KitApi(new KitApiOptions
+var kit = OpenIapClient.KitApi(new KitApiOptions
{
ApiKey = "iapkit_...",
BaseUrl = "https://kit.openiap.dev",
@@ -96,7 +102,7 @@ var status = await kit.StatusAsync("user-123");
var entitlements = await kit.EntitlementsAsync("user-123");
await kit.BindUserAsync(purchaseToken: "token", userId: "user-123");
-using var listener = Iap.ConnectWebhookStream(new WebhookListenerOptions
+using var listener = OpenIapClient.ConnectWebhookStream(new WebhookListenerOptions
{
ApiKey = "iapkit_...",
OnEvent = webhookEvent =>
@@ -109,7 +115,7 @@ using var listener = Iap.ConnectWebhookStream(new WebhookListenerOptions
},
});
-ParsedWebhookEventResult parsed = Iap.ParseWebhookEventData(rawSseData);
+ParsedWebhookEventResult parsed = OpenIapClient.ParseWebhookEventData(rawSseData);
```
## Example app
@@ -129,6 +135,9 @@ dotnet build -t:Run -f net9.0-ios
dotnet build -t:Run -f net9.0-maccatalyst
```
+For .NET 10 apps, use the matching `net10.0-android`, `net10.0-ios`, and
+`net10.0-maccatalyst` target frameworks.
+
VS Code launch configurations are in `libraries/maui-iap/.vscode/launch.json`.
The Android launcher builds both AARs before compiling the example app.
diff --git a/libraries/maui-iap/example/OpenIap.Maui.Example/Pages/AllProductsPage.xaml.cs b/libraries/maui-iap/example/OpenIap.Maui.Example/Pages/AllProductsPage.xaml.cs
index 5146251d..6c3b70c4 100644
--- a/libraries/maui-iap/example/OpenIap.Maui.Example/Pages/AllProductsPage.xaml.cs
+++ b/libraries/maui-iap/example/OpenIap.Maui.Example/Pages/AllProductsPage.xaml.cs
@@ -39,7 +39,7 @@ private async Task ConnectAndFetchAsync()
{
try
{
- var query = (QueryResolver)Iap.Instance;
+ var query = (QueryResolver)OpenIapClient.Instance;
await IapLifecycle.InitConnectionAsync();
ConnectionLabel.Text = "✅ Connected";
diff --git a/libraries/maui-iap/example/OpenIap.Maui.Example/Pages/AlternativeBillingPage.xaml.cs b/libraries/maui-iap/example/OpenIap.Maui.Example/Pages/AlternativeBillingPage.xaml.cs
index da2567f6..da8db5da 100644
--- a/libraries/maui-iap/example/OpenIap.Maui.Example/Pages/AlternativeBillingPage.xaml.cs
+++ b/libraries/maui-iap/example/OpenIap.Maui.Example/Pages/AlternativeBillingPage.xaml.cs
@@ -30,8 +30,8 @@ public AlternativeBillingPage()
protected override async void OnAppearing()
{
base.OnAppearing();
- _purchaseSub ??= Iap.Instance.PurchaseUpdated.Subscribe(p => MainThread.BeginInvokeOnMainThread(() => OnPurchase(p)));
- _errorSub ??= Iap.Instance.PurchaseError.Subscribe(err => MainThread.BeginInvokeOnMainThread(() => OnPurchaseError(err)));
+ _purchaseSub ??= OpenIapClient.Instance.PurchaseUpdated.Subscribe(p => MainThread.BeginInvokeOnMainThread(() => OnPurchase(p)));
+ _errorSub ??= OpenIapClient.Instance.PurchaseError.Subscribe(err => MainThread.BeginInvokeOnMainThread(() => OnPurchaseError(err)));
await ConnectAndFetchAsync();
}
@@ -75,7 +75,7 @@ private async Task LoadProductsAsync()
{
try
{
- var query = (QueryResolver)Iap.Instance;
+ var query = (QueryResolver)OpenIapClient.Instance;
var result = await query.FetchProductsAsync(new ProductRequest
{
Skus = Constants.ConsumableProductIds,
@@ -307,7 +307,7 @@ private async Task HandleIOSAlternativeBillingPurchaseAsync(Product product)
ShowResult("🌐 Opening external purchase link...");
try
{
- var mutate = (MutationResolver)Iap.Instance;
+ var mutate = (MutationResolver)OpenIapClient.Instance;
var result = await mutate.PresentExternalPurchaseLinkIOSAsync(externalUrl);
if (!string.IsNullOrEmpty(result.Error))
{
@@ -339,7 +339,7 @@ private async Task HandleAndroidBillingProgramsAsync(Product product)
try
{
- var mutate = (MutationResolver)Iap.Instance;
+ var mutate = (MutationResolver)OpenIapClient.Instance;
var availability = await mutate.IsBillingProgramAvailableAndroidAsync(_billingProgram);
if (!availability.IsAvailable)
{
@@ -384,7 +384,7 @@ private async Task HandleAndroidUserChoiceBillingAsync(Product product)
try
{
- var mutate = (MutationResolver)Iap.Instance;
+ var mutate = (MutationResolver)OpenIapClient.Instance;
await mutate.RequestPurchaseAsync(new RequestPurchaseProps
{
RequestPurchase = new RequestPurchasePropsByPlatforms
@@ -416,7 +416,7 @@ private async void OnPurchase(Purchase purchase)
try
{
- var mutate = (MutationResolver)Iap.Instance;
+ var mutate = (MutationResolver)OpenIapClient.Instance;
await mutate.FinishTransactionAsync(
purchase: new PurchaseInput(purchase),
isConsumable: Constants.ConsumableProductIdSet.Contains(common.ProductId));
diff --git a/libraries/maui-iap/example/OpenIap.Maui.Example/Pages/AvailablePurchasesPage.xaml.cs b/libraries/maui-iap/example/OpenIap.Maui.Example/Pages/AvailablePurchasesPage.xaml.cs
index 2e394d8c..fac8fb88 100644
--- a/libraries/maui-iap/example/OpenIap.Maui.Example/Pages/AvailablePurchasesPage.xaml.cs
+++ b/libraries/maui-iap/example/OpenIap.Maui.Example/Pages/AvailablePurchasesPage.xaml.cs
@@ -60,7 +60,7 @@ private async Task RefreshAsync()
try
{
- var query = (QueryResolver)Iap.Instance;
+ var query = (QueryResolver)OpenIapClient.Instance;
await query.FetchProductsAsync(new ProductRequest
{
Skus = Constants.SubscriptionProductIds,
@@ -170,7 +170,7 @@ private async void OnDeepLinkClicked(object sender, EventArgs e)
{
try
{
- var mutate = (MutationResolver)Iap.Instance;
+ var mutate = (MutationResolver)OpenIapClient.Instance;
var sku = _active.FirstOrDefault()?.ProductId ?? Constants.DefaultSubscriptionProductId;
await mutate.DeepLinkToSubscriptionsAsync(new DeepLinkOptions
{
@@ -194,7 +194,7 @@ private async Task RefreshStorefrontAsync(bool showAlert)
{
try
{
- var query = (QueryResolver)Iap.Instance;
+ var query = (QueryResolver)OpenIapClient.Instance;
var storefront = await query.GetStorefrontAsync();
StorefrontLabel.Text = $"Storefront: {storefront ?? string.Empty}";
StorefrontLabel.IsVisible = !string.IsNullOrEmpty(storefront);
diff --git a/libraries/maui-iap/example/OpenIap.Maui.Example/Pages/HomePage.xaml.cs b/libraries/maui-iap/example/OpenIap.Maui.Example/Pages/HomePage.xaml.cs
index 2e6c2b18..c0af6a0a 100644
--- a/libraries/maui-iap/example/OpenIap.Maui.Example/Pages/HomePage.xaml.cs
+++ b/libraries/maui-iap/example/OpenIap.Maui.Example/Pages/HomePage.xaml.cs
@@ -18,7 +18,7 @@ protected override async void OnAppearing()
try
{
- var query = (QueryResolver)Iap.Instance;
+ var query = (QueryResolver)OpenIapClient.Instance;
var storefront = await query.GetStorefrontAsync();
StorefrontLabel.Text = string.IsNullOrEmpty(storefront)
? "Best Practice Implementations"
diff --git a/libraries/maui-iap/example/OpenIap.Maui.Example/Pages/OfferCodePage.xaml.cs b/libraries/maui-iap/example/OpenIap.Maui.Example/Pages/OfferCodePage.xaml.cs
index d2d5aac5..26579744 100644
--- a/libraries/maui-iap/example/OpenIap.Maui.Example/Pages/OfferCodePage.xaml.cs
+++ b/libraries/maui-iap/example/OpenIap.Maui.Example/Pages/OfferCodePage.xaml.cs
@@ -58,7 +58,7 @@ private async void OnPresentClicked(object sender, EventArgs e)
#if IOS || MACCATALYST
try
{
- var mutate = (MutationResolver)Iap.Instance;
+ var mutate = (MutationResolver)OpenIapClient.Instance;
var presented = await mutate.PresentCodeRedemptionSheetIOSAsync();
ResultPanel.IsVisible = true;
ResultLabel.Text = presented
diff --git a/libraries/maui-iap/example/OpenIap.Maui.Example/Pages/PurchaseFlowPage.xaml.cs b/libraries/maui-iap/example/OpenIap.Maui.Example/Pages/PurchaseFlowPage.xaml.cs
index 07035288..af7d5a93 100644
--- a/libraries/maui-iap/example/OpenIap.Maui.Example/Pages/PurchaseFlowPage.xaml.cs
+++ b/libraries/maui-iap/example/OpenIap.Maui.Example/Pages/PurchaseFlowPage.xaml.cs
@@ -36,8 +36,8 @@ public PurchaseFlowPage()
protected override async void OnAppearing()
{
base.OnAppearing();
- _purchaseSub ??= Iap.Instance.PurchaseUpdated.Subscribe(p => MainThread.BeginInvokeOnMainThread(() => OnPurchase(p)));
- _errorSub ??= Iap.Instance.PurchaseError.Subscribe(err => MainThread.BeginInvokeOnMainThread(() => OnPurchaseError(err)));
+ _purchaseSub ??= OpenIapClient.Instance.PurchaseUpdated.Subscribe(p => MainThread.BeginInvokeOnMainThread(() => OnPurchase(p)));
+ _errorSub ??= OpenIapClient.Instance.PurchaseError.Subscribe(err => MainThread.BeginInvokeOnMainThread(() => OnPurchaseError(err)));
await ConnectAndFetchAsync();
}
@@ -81,7 +81,7 @@ private async Task LoadProductsAsync()
{
try
{
- var query = (QueryResolver)Iap.Instance;
+ var query = (QueryResolver)OpenIapClient.Instance;
var result = await query.FetchProductsAsync(new ProductRequest
{
Skus = Constants.ProductIds,
@@ -109,7 +109,7 @@ private async Task RefreshStorefrontAsync(bool showAlert = true)
StorefrontRefreshButton.Text = "Refreshing storefront...";
try
{
- var query = (QueryResolver)Iap.Instance;
+ var query = (QueryResolver)OpenIapClient.Instance;
var storefront = await query.GetStorefrontAsync().WaitAsync(TimeSpan.FromSeconds(10));
StorefrontValueLabel.Text = string.IsNullOrEmpty(storefront) ? "Not available" : storefront;
StorefrontErrorLabel.IsVisible = false;
@@ -140,7 +140,7 @@ private async Task RefreshAvailablePurchasesAsync(bool showAlert = true)
RefreshPurchasesButton.Text = "Refreshing purchases...";
try
{
- var query = (QueryResolver)Iap.Instance;
+ var query = (QueryResolver)OpenIapClient.Instance;
var purchases = await query.GetAvailablePurchasesAsync(new PurchaseOptions
{
OnlyIncludeActiveItemsIOS = true,
@@ -318,7 +318,7 @@ private async Task HandlePurchaseAsync(string sku)
RenderProducts();
try
{
- var mutate = (MutationResolver)Iap.Instance;
+ var mutate = (MutationResolver)OpenIapClient.Instance;
var requestTask = mutate.RequestPurchaseAsync(new RequestPurchaseProps
{
RequestPurchase = new RequestPurchasePropsByPlatforms
@@ -391,7 +391,7 @@ private async void OnPurchase(Purchase purchase)
{
try
{
- var mutate = (MutationResolver)Iap.Instance;
+ var mutate = (MutationResolver)OpenIapClient.Instance;
if (_verification == VerificationMethod.Local)
{
var result = await mutate.VerifyPurchaseAsync(new VerifyPurchaseProps
@@ -453,7 +453,7 @@ private static async Task FinishPurchaseTransactionAsync(Purchase purchase, bool
{
try
{
- var mutate = (MutationResolver)Iap.Instance;
+ var mutate = (MutationResolver)OpenIapClient.Instance;
await mutate.FinishTransactionAsync(
purchase: new PurchaseInput(purchase),
isConsumable: isConsumable).WaitAsync(TimeSpan.FromSeconds(10));
@@ -525,7 +525,7 @@ private async void OnCheckAppTransactionClicked(object sender, EventArgs e)
#if IOS || MACCATALYST
try
{
- var query = (QueryResolver)Iap.Instance;
+ var query = (QueryResolver)OpenIapClient.Instance;
var t = await query.GetAppTransactionIOSAsync();
if (t is null)
{
diff --git a/libraries/maui-iap/example/OpenIap.Maui.Example/Pages/SubscriptionFlowPage.xaml.cs b/libraries/maui-iap/example/OpenIap.Maui.Example/Pages/SubscriptionFlowPage.xaml.cs
index 292372f3..3cc3965b 100644
--- a/libraries/maui-iap/example/OpenIap.Maui.Example/Pages/SubscriptionFlowPage.xaml.cs
+++ b/libraries/maui-iap/example/OpenIap.Maui.Example/Pages/SubscriptionFlowPage.xaml.cs
@@ -48,8 +48,8 @@ public SubscriptionFlowPage()
protected override async void OnAppearing()
{
base.OnAppearing();
- _purchaseSub ??= Iap.Instance.PurchaseUpdated.Subscribe(p => MainThread.BeginInvokeOnMainThread(() => OnPurchase(p)));
- _errorSub ??= Iap.Instance.PurchaseError.Subscribe(err => MainThread.BeginInvokeOnMainThread(() => OnPurchaseError(err)));
+ _purchaseSub ??= OpenIapClient.Instance.PurchaseUpdated.Subscribe(p => MainThread.BeginInvokeOnMainThread(() => OnPurchase(p)));
+ _errorSub ??= OpenIapClient.Instance.PurchaseError.Subscribe(err => MainThread.BeginInvokeOnMainThread(() => OnPurchaseError(err)));
await ConnectAndFetchAsync();
}
@@ -95,7 +95,7 @@ private async Task FetchSubscriptionsAsync()
{
try
{
- var query = (QueryResolver)Iap.Instance;
+ var query = (QueryResolver)OpenIapClient.Instance;
var result = await query.FetchProductsAsync(new ProductRequest
{
Skus = Constants.SubscriptionProductIds,
@@ -125,7 +125,7 @@ private async Task RefreshActiveAsync(bool showAlert = true, bool renderSubscrip
RefreshActiveButton.Text = "Refreshing status...";
try
{
- var query = (QueryResolver)Iap.Instance;
+ var query = (QueryResolver)OpenIapClient.Instance;
var active = await query.GetActiveSubscriptionsAsync(Constants.SubscriptionProductIds)
.WaitAsync(TimeSpan.FromSeconds(20));
_active.Clear();
@@ -508,7 +508,7 @@ private async Task SubmitSubscriptionRequestAsync(ProductSubscription sub, Cance
try
{
SetActionStatus($"Dispatching StoreKit request: {common.Id}");
- var mutate = (MutationResolver)Iap.Instance;
+ var mutate = (MutationResolver)OpenIapClient.Instance;
var requestTask = mutate.RequestPurchaseAsync(new RequestPurchaseProps
{
RequestSubscription = new RequestSubscriptionPropsByPlatforms
@@ -669,7 +669,7 @@ private static async Task FinishSubscriptionTransactionAsync(Purchase purchase)
{
try
{
- var mutate = (MutationResolver)Iap.Instance;
+ var mutate = (MutationResolver)OpenIapClient.Instance;
await mutate.FinishTransactionAsync(
purchase: new PurchaseInput(purchase),
isConsumable: false).WaitAsync(TimeSpan.FromSeconds(10));
@@ -693,7 +693,7 @@ private async Task VerifySubscriptionIfNeededAsync(Purchase purchase)
try
{
- var mutate = (MutationResolver)Iap.Instance;
+ var mutate = (MutationResolver)OpenIapClient.Instance;
if (_verification == VerificationMethod.Local)
{
var result = await mutate.VerifyPurchaseAsync(new VerifyPurchaseProps
@@ -780,7 +780,7 @@ private async void OnManageClicked(object sender, EventArgs e)
{
try
{
- var mutate = (MutationResolver)Iap.Instance;
+ var mutate = (MutationResolver)OpenIapClient.Instance;
if (IsApplePlatform)
{
try
diff --git a/libraries/maui-iap/example/OpenIap.Maui.Example/Pages/WebhookStreamPage.xaml.cs b/libraries/maui-iap/example/OpenIap.Maui.Example/Pages/WebhookStreamPage.xaml.cs
index c4ddae9b..bbdb5cd5 100644
--- a/libraries/maui-iap/example/OpenIap.Maui.Example/Pages/WebhookStreamPage.xaml.cs
+++ b/libraries/maui-iap/example/OpenIap.Maui.Example/Pages/WebhookStreamPage.xaml.cs
@@ -46,7 +46,7 @@ private void OnConnectClicked(object sender, EventArgs e)
ResetEmptyLog();
Append($"→ Connecting {url}");
- _listener = Iap.ConnectWebhookStream(new WebhookListenerOptions
+ _listener = OpenIapClient.ConnectWebhookStream(new WebhookListenerOptions
{
ApiKey = apiKey,
BaseUrl = BaseUrlEntry.Text,
diff --git a/libraries/maui-iap/example/OpenIap.Maui.Example/Utils/IapLifecycle.cs b/libraries/maui-iap/example/OpenIap.Maui.Example/Utils/IapLifecycle.cs
index cfa2a6e4..475505fc 100644
--- a/libraries/maui-iap/example/OpenIap.Maui.Example/Utils/IapLifecycle.cs
+++ b/libraries/maui-iap/example/OpenIap.Maui.Example/Utils/IapLifecycle.cs
@@ -12,7 +12,7 @@ public static async Task InitConnectionAsync(InitConnectionConfig? config
await Gate.WaitAsync();
try
{
- var mutate = (MutationResolver)Iap.Instance;
+ var mutate = (MutationResolver)OpenIapClient.Instance;
return await mutate.InitConnectionAsync(config).WaitAsync(TimeSpan.FromSeconds(15));
}
finally
@@ -26,7 +26,7 @@ public static async Task EndConnectionQuietlyAsync(string owner)
await Gate.WaitAsync();
try
{
- var mutate = (MutationResolver)Iap.Instance;
+ var mutate = (MutationResolver)OpenIapClient.Instance;
await mutate.EndConnectionAsync().WaitAsync(TimeSpan.FromSeconds(5));
}
catch (Exception ex)
diff --git a/libraries/maui-iap/src/Directory.Build.props b/libraries/maui-iap/src/Directory.Build.props
index 7698bdb8..86435cbb 100644
--- a/libraries/maui-iap/src/Directory.Build.props
+++ b/libraries/maui-iap/src/Directory.Build.props
@@ -3,14 +3,13 @@
8.3.02.10.1
- 2.2.10
- 1.9.0.3
- 3.0.0
- 3.1.8
- 3.1.8
- 18.5.0
- 18.9.0
- 19.0.0
- 18.2.0
+ 8.3.0.2
+ 2.14.0
+ 1.12.4.1
+ 1.8.9.2
+ 2.10.0.2
+ 1.4.0.2
+ 2.3.10.1
+ 1.10.2.3
diff --git a/libraries/maui-iap/src/OpenIap.Maui.Bindings.Android/OpenIap.Maui.Bindings.Android.csproj b/libraries/maui-iap/src/OpenIap.Maui.Bindings.Android/OpenIap.Maui.Bindings.Android.csproj
index 2cb2faab..9ca24264 100644
--- a/libraries/maui-iap/src/OpenIap.Maui.Bindings.Android/OpenIap.Maui.Bindings.Android.csproj
+++ b/libraries/maui-iap/src/OpenIap.Maui.Bindings.Android/OpenIap.Maui.Bindings.Android.csproj
@@ -1,7 +1,7 @@
- net9.0-android
+ net9.0-android;net10.0-android24.0OpenIap.Maui.Bindings.AndroidOpenIap.Maui.Bindings.Android
@@ -17,7 +17,7 @@
deliberate to surface each subtype's own field set.
BG86xx-BG88xx: known Java-binding noise about renamed/wrapped events.
-->
-
@@ -39,30 +39,13 @@
-
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
diff --git a/libraries/maui-iap/src/OpenIap.Maui.Bindings.iOS/OpenIap.Maui.Bindings.iOS.csproj b/libraries/maui-iap/src/OpenIap.Maui.Bindings.iOS/OpenIap.Maui.Bindings.iOS.csproj
index d2388a2d..33ee884e 100644
--- a/libraries/maui-iap/src/OpenIap.Maui.Bindings.iOS/OpenIap.Maui.Bindings.iOS.csproj
+++ b/libraries/maui-iap/src/OpenIap.Maui.Bindings.iOS/OpenIap.Maui.Bindings.iOS.csproj
@@ -1,7 +1,7 @@
- net9.0-ios;net9.0-maccatalyst
+ net9.0-ios;net10.0-ios;net9.0-maccatalyst;net10.0-maccatalyst15.015.0OpenIap.Maui.Bindings.iOS
diff --git a/libraries/maui-iap/src/OpenIap.Maui/OpenIap.Maui.csproj b/libraries/maui-iap/src/OpenIap.Maui/OpenIap.Maui.csproj
index 92b6fc3f..c3ffaaff 100644
--- a/libraries/maui-iap/src/OpenIap.Maui/OpenIap.Maui.csproj
+++ b/libraries/maui-iap/src/OpenIap.Maui/OpenIap.Maui.csproj
@@ -1,9 +1,9 @@
- net9.0;net9.0-android;net9.0-ios;net9.0-maccatalyst
+ net9.0;net10.0;net9.0-android;net10.0-android;net9.0-ios;net10.0-ios;net9.0-maccatalyst;net10.0-maccatalyst
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -80,11 +91,10 @@
-
+
$(TargetsForTfmSpecificBuildOutput);IncludeBindingsInNuPkg$(AllowedOutputExtensionsInPackageBuildOutputFolder);.aar;.zip
@@ -93,8 +103,8 @@
-
-
+
+
diff --git a/libraries/maui-iap/src/OpenIap.Maui/OpenIap.cs b/libraries/maui-iap/src/OpenIap.Maui/OpenIap.cs
index b2cbde0b..6c5da9db 100644
--- a/libraries/maui-iap/src/OpenIap.Maui/OpenIap.cs
+++ b/libraries/maui-iap/src/OpenIap.Maui/OpenIap.cs
@@ -2,11 +2,11 @@
// OpenIAP — public API surface for .NET MAUI
// ============================================================================
//
-// The static `Iap` class is the recommended entry point. It delegates to a
-// platform implementation that is selected at compile time (see the
-// Platforms/ folder). The class is named `Iap` (not `OpenIap`) to avoid
-// shadowing the `OpenIap` namespace when consumers `using` both
-// `OpenIap` and `OpenIap.Maui`. Mirrors the API surface of:
+// The static `OpenIapClient` class is the recommended entry point. It
+// delegates to a platform implementation that is selected at compile time (see
+// the Platforms/ folder). The older `Iap` facade remains as a compatibility
+// shim, but the longer name avoids collisions with app namespaces such as
+// `OpenIap.Maui.Iap`. Mirrors the API surface of:
// - react-native-iap / expo-iap (TypeScript)
// - flutter_inapp_purchase (Dart)
// - kmp-iap (Kotlin)
@@ -25,6 +25,7 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel;
using System.Threading.Tasks;
using OpenIap;
@@ -90,10 +91,10 @@ public interface IOpenIap
///
/// Static convenience facade. Resolves the platform implementation lazily so
-/// host apps can write await Iap.Instance.FetchProductsAsync(...)
+/// host apps can write await OpenIapClient.Instance.FetchProductsAsync(...)
/// once the platform impl also implements QueryResolver.
///
-public static class Iap
+public static class OpenIapClient
{
private static IOpenIap? _instance;
@@ -153,6 +154,38 @@ public static ParsedWebhookEventResult ParseWebhookEventData(string raw)
=> WebhookClient.ParseWebhookEventData(raw);
}
+///
+/// Backward-compatible alias for . New code should
+/// use to avoid namespace/type name collisions in
+/// projects whose namespaces start with OpenIap.Maui.Iap.
+///
+[EditorBrowsable(EditorBrowsableState.Never)]
+public static class Iap
+{
+ ///
+ public static IOpenIap Instance => OpenIapClient.Instance;
+
+ ///
+ public static void OverrideInstance(IOpenIap instance)
+ => OpenIapClient.OverrideInstance(instance);
+
+ ///
+ public static KitApiClient KitApi(KitApiOptions options)
+ => OpenIapClient.KitApi(options);
+
+ ///
+ public static WebhookListener ConnectWebhookStream(WebhookListenerOptions options)
+ => OpenIapClient.ConnectWebhookStream(options);
+
+ ///
+ public static IReadOnlyList WebhookEventTypes
+ => OpenIapClient.WebhookEventTypes;
+
+ ///
+ public static ParsedWebhookEventResult ParseWebhookEventData(string raw)
+ => OpenIapClient.ParseWebhookEventData(raw);
+}
+
///
/// Platform factory. The actual implementation is provided by the
/// per-platform OpenIapPlatform.<platform>.cs file. The
diff --git a/llms-full.txt b/llms-full.txt
index 561c961e..86d8fb5c 100644
--- a/llms-full.txt
+++ b/llms-full.txt
@@ -3,7 +3,7 @@
> OpenIAP: Unified in-app purchase specification for iOS & Android
> Documentation: https://openiap.dev
> Quick Reference: https://openiap.dev/llms.txt
-> Generated: 2026-05-16T12:59:43.331Z
+> Generated: 2026-06-15T14:57:16.350Z
## Table of Contents
1. Installation
@@ -30,19 +30,19 @@ cd ios && pod install
### Swift (iOS/macOS)
```swift
// Swift Package Manager
-.package(url: "https://github.com/hyodotdev/openiap.git", from: "2.1.9")
+.package(url: "https://github.com/hyodotdev/openiap.git", from: "2.2.1")
// CocoaPods
-pod 'openiap', '~> 2.1.9'
+pod 'openiap', '~> 2.2.1'
```
### Kotlin (Android)
```kotlin
// Gradle (build.gradle.kts)
-implementation("io.github.hyochan.openiap:openiap-google:2.1.5")
+implementation("io.github.hyochan.openiap:openiap-google:2.2.1")
// For Meta Horizon OS
-implementation("io.github.hyochan.openiap:openiap-google-horizon:2.1.5")
+implementation("io.github.hyochan.openiap:openiap-google-horizon:2.2.1")
```
### Flutter
@@ -51,13 +51,13 @@ flutter pub add flutter_inapp_purchase
```
### Godot
-Download `godot-iap-2.2.10.zip` from GitHub Releases, extract it to
+Download `godot-iap-2.3.1.zip` from GitHub Releases, extract it to
`addons/godot-iap/`, then enable the plugin in Project Settings.
### Kotlin Multiplatform
```kotlin
dependencies {
- implementation("io.github.hyochan:kmp-iap:2.2.8")
+ implementation("io.github.hyochan:kmp-iap:2.3.1")
}
```
@@ -69,9 +69,9 @@ https://central.sonatype.com/artifact/io.github.hyochan/kmp-iap
dotnet add package OpenIap.Maui
```
-Current NuGet package version: 1.0.4
+Current NuGet package version: 1.1.1
-Requires .NET 9+, the MAUI workload, iOS 15.0+, and Android API 24+.
+Requires .NET 9 or .NET 10, the MAUI workload, iOS 15.0+, and Android API 24+.
---
@@ -117,20 +117,23 @@ Requires .NET 9+, the MAUI workload, iOS 15.0+, and Android API 24+.
are private implementation details and are flattened into `OpenIap.Maui`
instead of being published as separate package dependencies.
- Implementation: .NET MAUI projection with generated `Types.cs`, a static
- `Iap.Instance` facade, `IOpenIap` observables, and per-platform resolvers.
+ `OpenIapClient.Instance` facade, legacy `Iap` shim, `IOpenIap`
+ observables, and per-platform resolvers.
- iOS/macCatalyst bridge: .NET-for-iOS binding over
`OpenIAP.xcframework` and `OpenIapModule+ObjC.swift`; NuGet consumers get
the official `OpenIap.Maui.Bindings.iOS.resources.zip` sidecar so no
app-level `NativeReference` is required.
- Android bridge: Xamarin.Android binding over the MAUI-owned
`openiap-release.aar`, which wraps the unbound
- `openiap-play-release.aar` runtime dependency; resolved BillingClient /
- Play Services AARs are included in the main nupkg for app packaging.
+ `openiap-play-release.aar` runtime dependency. Google Billing, Play
+ Services, Gson, AndroidX, and Kotlin Android libraries stay as NuGet
+ `PackageReference` dependencies so consuming apps can deduplicate them.
- Public surface: `QueryResolver`, `MutationResolver`, and `IOpenIap`
implemented by `OpenIapIOS`, `OpenIapAndroid`, and `OpenIapMacCatalyst`;
- IAPKit helpers mirror the TypeScript SDKs via `Iap.KitApi(...)`,
- `Iap.ConnectWebhookStream(...)`, `Iap.ParseWebhookEventData(...)`, and
- `Iap.WebhookEventTypes`.
+ IAPKit helpers mirror the TypeScript SDKs via
+ `OpenIapClient.KitApi(...)`, `OpenIapClient.ConnectWebhookStream(...)`,
+ `OpenIapClient.ParseWebhookEventData(...)`, and
+ `OpenIapClient.WebhookEventTypes`.
- Example app: `libraries/maui-iap/example/OpenIap.Maui.Example`, mirroring
the `expo-iap` example flows.
@@ -191,7 +194,7 @@ iap.purchaseUpdatedListener.collect { purchase ->
using OpenIap;
using OpenIap.Maui;
-var iap = Iap.Instance;
+var iap = OpenIapClient.Instance;
await ((MutationResolver)iap).InitConnectionAsync();
await ((QueryResolver)iap).FetchProductsAsync(new ProductRequest
diff --git a/llms.txt b/llms.txt
index 45cfb2e2..7194caf9 100644
--- a/llms.txt
+++ b/llms.txt
@@ -3,7 +3,7 @@
> OpenIAP: Unified in-app purchase specification for iOS & Android
> Documentation: https://openiap.dev
> Full Reference: https://openiap.dev/llms-full.txt
-> Generated: 2026-05-16T12:59:43.331Z
+> Generated: 2026-06-15T14:57:16.350Z
## Installation
@@ -19,12 +19,12 @@ npm install react-native-iap
### Native
```swift
// Swift Package Manager
-.package(url: "https://github.com/hyodotdev/openiap.git", from: "2.1.9")
+.package(url: "https://github.com/hyodotdev/openiap.git", from: "2.2.1")
```
```kotlin
// Gradle
-implementation("io.github.hyochan.openiap:openiap-google:2.1.5")
+implementation("io.github.hyochan.openiap:openiap-google:2.2.1")
```
```bash
@@ -34,12 +34,12 @@ flutter pub add flutter_inapp_purchase
```gdscript
# Godot
-# Install godot-iap 2.2.10 to addons/godot-iap and enable the plugin
+# Install godot-iap 2.3.1 to addons/godot-iap and enable the plugin
```
```kotlin
// Kotlin Multiplatform
-implementation("io.github.hyochan:kmp-iap:2.2.8")
+implementation("io.github.hyochan:kmp-iap:2.3.1")
```
```bash
@@ -47,7 +47,7 @@ implementation("io.github.hyochan:kmp-iap:2.2.8")
dotnet add package OpenIap.Maui
```
-Current NuGet package version: 1.0.4
+Current NuGet package version: 1.1.1
## Framework Libraries
@@ -56,11 +56,13 @@ Current NuGet package version: 1.0.4
- `flutter_inapp_purchase`: Dart API with generated OpenIAP types and streams.
- `godot-iap`: Godot 4.x plugin with GDScript functions and signals.
- `kmp-iap`: Kotlin Multiplatform API with Flow-based purchase events.
-- `maui-iap`: `OpenIap.Maui` package with `Iap.Instance`,
- generated `Types.cs`, IAPKit helpers (`Iap.KitApi`,
- `Iap.ConnectWebhookStream`, `Iap.ParseWebhookEventData`), flattened iOS
- xcframework / Android AAR bindings in one NuGet package, and MAUI example
- flows matching `expo-iap`.
+- `maui-iap`: `OpenIap.Maui` package with `OpenIapClient.Instance`,
+ generated `Types.cs`, IAPKit helpers (`OpenIapClient.KitApi`,
+ `OpenIapClient.ConnectWebhookStream`,
+ `OpenIapClient.ParseWebhookEventData`), flattened OpenIAP-owned iOS
+ xcframework / Android AAR bindings, Google and AndroidX Android
+ dependencies as NuGet package references, and MAUI example flows matching
+ `expo-iap`.
## Core APIs
diff --git a/packages/docs/public/llms-full.txt b/packages/docs/public/llms-full.txt
index 561c961e..86d8fb5c 100644
--- a/packages/docs/public/llms-full.txt
+++ b/packages/docs/public/llms-full.txt
@@ -3,7 +3,7 @@
> OpenIAP: Unified in-app purchase specification for iOS & Android
> Documentation: https://openiap.dev
> Quick Reference: https://openiap.dev/llms.txt
-> Generated: 2026-05-16T12:59:43.331Z
+> Generated: 2026-06-15T14:57:16.350Z
## Table of Contents
1. Installation
@@ -30,19 +30,19 @@ cd ios && pod install
### Swift (iOS/macOS)
```swift
// Swift Package Manager
-.package(url: "https://github.com/hyodotdev/openiap.git", from: "2.1.9")
+.package(url: "https://github.com/hyodotdev/openiap.git", from: "2.2.1")
// CocoaPods
-pod 'openiap', '~> 2.1.9'
+pod 'openiap', '~> 2.2.1'
```
### Kotlin (Android)
```kotlin
// Gradle (build.gradle.kts)
-implementation("io.github.hyochan.openiap:openiap-google:2.1.5")
+implementation("io.github.hyochan.openiap:openiap-google:2.2.1")
// For Meta Horizon OS
-implementation("io.github.hyochan.openiap:openiap-google-horizon:2.1.5")
+implementation("io.github.hyochan.openiap:openiap-google-horizon:2.2.1")
```
### Flutter
@@ -51,13 +51,13 @@ flutter pub add flutter_inapp_purchase
```
### Godot
-Download `godot-iap-2.2.10.zip` from GitHub Releases, extract it to
+Download `godot-iap-2.3.1.zip` from GitHub Releases, extract it to
`addons/godot-iap/`, then enable the plugin in Project Settings.
### Kotlin Multiplatform
```kotlin
dependencies {
- implementation("io.github.hyochan:kmp-iap:2.2.8")
+ implementation("io.github.hyochan:kmp-iap:2.3.1")
}
```
@@ -69,9 +69,9 @@ https://central.sonatype.com/artifact/io.github.hyochan/kmp-iap
dotnet add package OpenIap.Maui
```
-Current NuGet package version: 1.0.4
+Current NuGet package version: 1.1.1
-Requires .NET 9+, the MAUI workload, iOS 15.0+, and Android API 24+.
+Requires .NET 9 or .NET 10, the MAUI workload, iOS 15.0+, and Android API 24+.
---
@@ -117,20 +117,23 @@ Requires .NET 9+, the MAUI workload, iOS 15.0+, and Android API 24+.
are private implementation details and are flattened into `OpenIap.Maui`
instead of being published as separate package dependencies.
- Implementation: .NET MAUI projection with generated `Types.cs`, a static
- `Iap.Instance` facade, `IOpenIap` observables, and per-platform resolvers.
+ `OpenIapClient.Instance` facade, legacy `Iap` shim, `IOpenIap`
+ observables, and per-platform resolvers.
- iOS/macCatalyst bridge: .NET-for-iOS binding over
`OpenIAP.xcframework` and `OpenIapModule+ObjC.swift`; NuGet consumers get
the official `OpenIap.Maui.Bindings.iOS.resources.zip` sidecar so no
app-level `NativeReference` is required.
- Android bridge: Xamarin.Android binding over the MAUI-owned
`openiap-release.aar`, which wraps the unbound
- `openiap-play-release.aar` runtime dependency; resolved BillingClient /
- Play Services AARs are included in the main nupkg for app packaging.
+ `openiap-play-release.aar` runtime dependency. Google Billing, Play
+ Services, Gson, AndroidX, and Kotlin Android libraries stay as NuGet
+ `PackageReference` dependencies so consuming apps can deduplicate them.
- Public surface: `QueryResolver`, `MutationResolver`, and `IOpenIap`
implemented by `OpenIapIOS`, `OpenIapAndroid`, and `OpenIapMacCatalyst`;
- IAPKit helpers mirror the TypeScript SDKs via `Iap.KitApi(...)`,
- `Iap.ConnectWebhookStream(...)`, `Iap.ParseWebhookEventData(...)`, and
- `Iap.WebhookEventTypes`.
+ IAPKit helpers mirror the TypeScript SDKs via
+ `OpenIapClient.KitApi(...)`, `OpenIapClient.ConnectWebhookStream(...)`,
+ `OpenIapClient.ParseWebhookEventData(...)`, and
+ `OpenIapClient.WebhookEventTypes`.
- Example app: `libraries/maui-iap/example/OpenIap.Maui.Example`, mirroring
the `expo-iap` example flows.
@@ -191,7 +194,7 @@ iap.purchaseUpdatedListener.collect { purchase ->
using OpenIap;
using OpenIap.Maui;
-var iap = Iap.Instance;
+var iap = OpenIapClient.Instance;
await ((MutationResolver)iap).InitConnectionAsync();
await ((QueryResolver)iap).FetchProductsAsync(new ProductRequest
diff --git a/packages/docs/public/llms.txt b/packages/docs/public/llms.txt
index 45cfb2e2..7194caf9 100644
--- a/packages/docs/public/llms.txt
+++ b/packages/docs/public/llms.txt
@@ -3,7 +3,7 @@
> OpenIAP: Unified in-app purchase specification for iOS & Android
> Documentation: https://openiap.dev
> Full Reference: https://openiap.dev/llms-full.txt
-> Generated: 2026-05-16T12:59:43.331Z
+> Generated: 2026-06-15T14:57:16.350Z
## Installation
@@ -19,12 +19,12 @@ npm install react-native-iap
### Native
```swift
// Swift Package Manager
-.package(url: "https://github.com/hyodotdev/openiap.git", from: "2.1.9")
+.package(url: "https://github.com/hyodotdev/openiap.git", from: "2.2.1")
```
```kotlin
// Gradle
-implementation("io.github.hyochan.openiap:openiap-google:2.1.5")
+implementation("io.github.hyochan.openiap:openiap-google:2.2.1")
```
```bash
@@ -34,12 +34,12 @@ flutter pub add flutter_inapp_purchase
```gdscript
# Godot
-# Install godot-iap 2.2.10 to addons/godot-iap and enable the plugin
+# Install godot-iap 2.3.1 to addons/godot-iap and enable the plugin
```
```kotlin
// Kotlin Multiplatform
-implementation("io.github.hyochan:kmp-iap:2.2.8")
+implementation("io.github.hyochan:kmp-iap:2.3.1")
```
```bash
@@ -47,7 +47,7 @@ implementation("io.github.hyochan:kmp-iap:2.2.8")
dotnet add package OpenIap.Maui
```
-Current NuGet package version: 1.0.4
+Current NuGet package version: 1.1.1
## Framework Libraries
@@ -56,11 +56,13 @@ Current NuGet package version: 1.0.4
- `flutter_inapp_purchase`: Dart API with generated OpenIAP types and streams.
- `godot-iap`: Godot 4.x plugin with GDScript functions and signals.
- `kmp-iap`: Kotlin Multiplatform API with Flow-based purchase events.
-- `maui-iap`: `OpenIap.Maui` package with `Iap.Instance`,
- generated `Types.cs`, IAPKit helpers (`Iap.KitApi`,
- `Iap.ConnectWebhookStream`, `Iap.ParseWebhookEventData`), flattened iOS
- xcframework / Android AAR bindings in one NuGet package, and MAUI example
- flows matching `expo-iap`.
+- `maui-iap`: `OpenIap.Maui` package with `OpenIapClient.Instance`,
+ generated `Types.cs`, IAPKit helpers (`OpenIapClient.KitApi`,
+ `OpenIapClient.ConnectWebhookStream`,
+ `OpenIapClient.ParseWebhookEventData`), flattened OpenIAP-owned iOS
+ xcframework / Android AAR bindings, Google and AndroidX Android
+ dependencies as NuGet package references, and MAUI example flows matching
+ `expo-iap`.
## Core APIs
diff --git a/packages/docs/scripts/replace-csharp-tabs.mjs b/packages/docs/scripts/replace-csharp-tabs.mjs
index d0ac213e..1f7fc4bc 100644
--- a/packages/docs/scripts/replace-csharp-tabs.mjs
+++ b/packages/docs/scripts/replace-csharp-tabs.mjs
@@ -38,13 +38,13 @@ function kotlinToCSharp(kotlin) {
);
// Call sites: `openIapStore.x(`, `kmpIAP.x(`, `kmpIapInstance.x(`, `iap.x(` →
- // `await ((QueryResolver)Iap.Instance).XAsync(` (best-effort; reader can
+ // `await ((QueryResolver)OpenIapClient.Instance).XAsync(` (best-effort; reader can
// swap to MutationResolver where appropriate).
s = s.replace(
/\b(openIapStore|kmpIAP|kmpIapInstance|iap)\.([a-z][A-Za-z0-9]*)\(/g,
(_m, _recv, method) => {
const csName = method[0].toUpperCase() + method.slice(1) + 'Async';
- return `await ((QueryResolver)Iap.Instance).${csName}(`;
+ return `await ((QueryResolver)OpenIapClient.Instance).${csName}(`;
}
);
diff --git a/packages/docs/src/pages/docs/apis/android/acknowledge-purchase-android.tsx b/packages/docs/src/pages/docs/apis/android/acknowledge-purchase-android.tsx
index c2b47a15..496acf79 100644
--- a/packages/docs/src/pages/docs/apis/android/acknowledge-purchase-android.tsx
+++ b/packages/docs/src/pages/docs/apis/android/acknowledge-purchase-android.tsx
@@ -128,7 +128,7 @@ if (Platform.OS === 'android') {
{`using OpenIap;
using OpenIap.Maui;
-await ((MutationResolver)Iap.Instance).AcknowledgePurchaseAndroidAsync(purchase.PurchaseToken);`}
+await ((MutationResolver)OpenIapClient.Instance).AcknowledgePurchaseAndroidAsync(purchase.PurchaseToken);`}
),
gdscript: (
{`if iap.get_platform() == "Android":
diff --git a/packages/docs/src/pages/docs/apis/android/check-alternative-billing-availability-android.tsx b/packages/docs/src/pages/docs/apis/android/check-alternative-billing-availability-android.tsx
index 37116294..3e16ed4b 100644
--- a/packages/docs/src/pages/docs/apis/android/check-alternative-billing-availability-android.tsx
+++ b/packages/docs/src/pages/docs/apis/android/check-alternative-billing-availability-android.tsx
@@ -102,7 +102,7 @@ if (Platform.OS === 'android') {
{`using OpenIap;
using OpenIap.Maui;
-var ok = await ((MutationResolver)Iap.Instance).CheckAlternativeBillingAvailabilityAndroidAsync();`}
+var ok = await ((MutationResolver)OpenIapClient.Instance).CheckAlternativeBillingAvailabilityAndroidAsync();`}
),
gdscript: (
{`if iap.get_platform() == "Android":
diff --git a/packages/docs/src/pages/docs/apis/android/consume-purchase-android.tsx b/packages/docs/src/pages/docs/apis/android/consume-purchase-android.tsx
index 6c95fe55..99823d03 100644
--- a/packages/docs/src/pages/docs/apis/android/consume-purchase-android.tsx
+++ b/packages/docs/src/pages/docs/apis/android/consume-purchase-android.tsx
@@ -125,7 +125,7 @@ if (Platform.OS === 'android') {
{`using OpenIap;
using OpenIap.Maui;
-await ((MutationResolver)Iap.Instance).ConsumePurchaseAndroidAsync(purchase.PurchaseToken);`}
+await ((MutationResolver)OpenIapClient.Instance).ConsumePurchaseAndroidAsync(purchase.PurchaseToken);`}
),
gdscript: (
{`if iap.get_platform() == "Android":
diff --git a/packages/docs/src/pages/docs/apis/android/create-alternative-billing-token-android.tsx b/packages/docs/src/pages/docs/apis/android/create-alternative-billing-token-android.tsx
index 32fce7bc..0483006c 100644
--- a/packages/docs/src/pages/docs/apis/android/create-alternative-billing-token-android.tsx
+++ b/packages/docs/src/pages/docs/apis/android/create-alternative-billing-token-android.tsx
@@ -105,7 +105,7 @@ if (Platform.OS === 'android') {
{`using OpenIap;
using OpenIap.Maui;
-var token = await ((MutationResolver)Iap.Instance).CreateAlternativeBillingTokenAndroidAsync();`}
+var token = await ((MutationResolver)OpenIapClient.Instance).CreateAlternativeBillingTokenAndroidAsync();`}
),
gdscript: (
{`if iap.get_platform() == "Android":
diff --git a/packages/docs/src/pages/docs/apis/android/create-billing-program-reporting-details-android.tsx b/packages/docs/src/pages/docs/apis/android/create-billing-program-reporting-details-android.tsx
index 3efc477f..b7e76800 100644
--- a/packages/docs/src/pages/docs/apis/android/create-billing-program-reporting-details-android.tsx
+++ b/packages/docs/src/pages/docs/apis/android/create-billing-program-reporting-details-android.tsx
@@ -168,7 +168,7 @@ if (Platform.OS === 'android') {
{`using OpenIap;
using OpenIap.Maui;
-var details = await ((MutationResolver)Iap.Instance).CreateBillingProgramReportingDetailsAndroidAsync(
+var details = await ((MutationResolver)OpenIapClient.Instance).CreateBillingProgramReportingDetailsAndroidAsync(
BillingProgramAndroid.ExternalOffer
);`}
),
diff --git a/packages/docs/src/pages/docs/apis/android/enable-billing-program-android.tsx b/packages/docs/src/pages/docs/apis/android/enable-billing-program-android.tsx
index 0e5d71ee..5a6085af 100644
--- a/packages/docs/src/pages/docs/apis/android/enable-billing-program-android.tsx
+++ b/packages/docs/src/pages/docs/apis/android/enable-billing-program-android.tsx
@@ -171,7 +171,7 @@ function App() {
{`using OpenIap;
using OpenIap.Maui;
-await ((MutationResolver)Iap.Instance).InitConnectionAsync(
+await ((MutationResolver)OpenIapClient.Instance).InitConnectionAsync(
new InitConnectionConfig
{
EnableBillingProgramAndroid = BillingProgramAndroid.ExternalOffer,
diff --git a/packages/docs/src/pages/docs/apis/android/is-billing-program-available-android.tsx b/packages/docs/src/pages/docs/apis/android/is-billing-program-available-android.tsx
index aced401c..c272a218 100644
--- a/packages/docs/src/pages/docs/apis/android/is-billing-program-available-android.tsx
+++ b/packages/docs/src/pages/docs/apis/android/is-billing-program-available-android.tsx
@@ -157,7 +157,7 @@ if (Platform.OS === 'android') {
{`using OpenIap;
using OpenIap.Maui;
-var result = await ((MutationResolver)Iap.Instance).IsBillingProgramAvailableAndroidAsync(
+var result = await ((MutationResolver)OpenIapClient.Instance).IsBillingProgramAvailableAndroidAsync(
BillingProgramAndroid.ExternalOffer
);`}
),
diff --git a/packages/docs/src/pages/docs/apis/android/launch-external-link-android.tsx b/packages/docs/src/pages/docs/apis/android/launch-external-link-android.tsx
index 8344dbc7..4d4f7d13 100644
--- a/packages/docs/src/pages/docs/apis/android/launch-external-link-android.tsx
+++ b/packages/docs/src/pages/docs/apis/android/launch-external-link-android.tsx
@@ -201,7 +201,7 @@ if (Platform.OS === 'android') {
{`using OpenIap;
using OpenIap.Maui;
-await ((MutationResolver)Iap.Instance).LaunchExternalLinkAndroidAsync(
+await ((MutationResolver)OpenIapClient.Instance).LaunchExternalLinkAndroidAsync(
new LaunchExternalLinkParamsAndroid
{
BillingProgram = BillingProgramAndroid.ExternalOffer,
diff --git a/packages/docs/src/pages/docs/apis/android/show-alternative-billing-dialog-android.tsx b/packages/docs/src/pages/docs/apis/android/show-alternative-billing-dialog-android.tsx
index 22596421..093a6400 100644
--- a/packages/docs/src/pages/docs/apis/android/show-alternative-billing-dialog-android.tsx
+++ b/packages/docs/src/pages/docs/apis/android/show-alternative-billing-dialog-android.tsx
@@ -106,7 +106,7 @@ if (Platform.OS === 'android') {
{`using OpenIap;
using OpenIap.Maui;
-var accepted = await ((MutationResolver)Iap.Instance).ShowAlternativeBillingDialogAndroidAsync();`}
+var accepted = await ((MutationResolver)OpenIapClient.Instance).ShowAlternativeBillingDialogAndroidAsync();`}
),
gdscript: (
{`if iap.get_platform() == "Android":
diff --git a/packages/docs/src/pages/docs/apis/deep-link-to-subscriptions.tsx b/packages/docs/src/pages/docs/apis/deep-link-to-subscriptions.tsx
index a8d47164..951d35ee 100644
--- a/packages/docs/src/pages/docs/apis/deep-link-to-subscriptions.tsx
+++ b/packages/docs/src/pages/docs/apis/deep-link-to-subscriptions.tsx
@@ -195,7 +195,7 @@ function ManageSubscriptionsButton() {
{`using OpenIap;
using OpenIap.Maui;
-await ((MutationResolver)Iap.Instance).DeepLinkToSubscriptionsAsync(
+await ((MutationResolver)OpenIapClient.Instance).DeepLinkToSubscriptionsAsync(
new DeepLinkOptions
{
SkuAndroid = "com.app.premium",
diff --git a/packages/docs/src/pages/docs/apis/end-connection.tsx b/packages/docs/src/pages/docs/apis/end-connection.tsx
index 6065d894..83ca32f3 100644
--- a/packages/docs/src/pages/docs/apis/end-connection.tsx
+++ b/packages/docs/src/pages/docs/apis/end-connection.tsx
@@ -125,7 +125,7 @@ function PurchaseScreen() {
{`using OpenIap;
using OpenIap.Maui;
-await ((MutationResolver)Iap.Instance).EndConnectionAsync();`}
+await ((MutationResolver)OpenIapClient.Instance).EndConnectionAsync();`}
),
gdscript: (
{`# In _exit_tree or cleanup
diff --git a/packages/docs/src/pages/docs/apis/fetch-products.tsx b/packages/docs/src/pages/docs/apis/fetch-products.tsx
index 479634ba..47a6d864 100644
--- a/packages/docs/src/pages/docs/apis/fetch-products.tsx
+++ b/packages/docs/src/pages/docs/apis/fetch-products.tsx
@@ -304,7 +304,7 @@ var products = await iap.fetch_products(request)`}{`using OpenIap;
using OpenIap.Maui;
-var iap = (QueryResolver)Iap.Instance;
+var iap = (QueryResolver)OpenIapClient.Instance;
var result = await iap.FetchProductsAsync(new ProductRequest {
Skus = new[] { "com.app.coins_100", "com.app.premium" },
diff --git a/packages/docs/src/pages/docs/apis/finish-transaction.tsx b/packages/docs/src/pages/docs/apis/finish-transaction.tsx
index e48c3d60..85ff419a 100644
--- a/packages/docs/src/pages/docs/apis/finish-transaction.tsx
+++ b/packages/docs/src/pages/docs/apis/finish-transaction.tsx
@@ -193,7 +193,7 @@ kmpIAP.finishTransaction(
{`using OpenIap;
using OpenIap.Maui;
-await ((MutationResolver)Iap.Instance).FinishTransactionAsync(
+await ((MutationResolver)OpenIapClient.Instance).FinishTransactionAsync(
purchase: new PurchaseInput(purchase),
isConsumable: true);`}
),
diff --git a/packages/docs/src/pages/docs/apis/get-active-subscriptions.tsx b/packages/docs/src/pages/docs/apis/get-active-subscriptions.tsx
index 30da4b6e..1cc5a1bf 100644
--- a/packages/docs/src/pages/docs/apis/get-active-subscriptions.tsx
+++ b/packages/docs/src/pages/docs/apis/get-active-subscriptions.tsx
@@ -207,7 +207,7 @@ function SubscriptionStatus() {
{`using OpenIap;
using OpenIap.Maui;
-var subscriptions = await ((QueryResolver)Iap.Instance).GetActiveSubscriptionsAsync()`}
+var subscriptions = await ((QueryResolver)OpenIapClient.Instance).GetActiveSubscriptionsAsync()`}
),
gdscript: (
{`var subscriptions = await iap.get_active_subscriptions()`}
diff --git a/packages/docs/src/pages/docs/apis/get-available-purchases.tsx b/packages/docs/src/pages/docs/apis/get-available-purchases.tsx
index 6db96c73..415cf0f8 100644
--- a/packages/docs/src/pages/docs/apis/get-available-purchases.tsx
+++ b/packages/docs/src/pages/docs/apis/get-available-purchases.tsx
@@ -192,7 +192,7 @@ function PendingPurchases() {
{`using OpenIap;
using OpenIap.Maui;
-var purchases = await ((QueryResolver)Iap.Instance).GetAvailablePurchasesAsync()`}
+var purchases = await ((QueryResolver)OpenIapClient.Instance).GetAvailablePurchasesAsync()`}
),
gdscript: (
{`var purchases = await iap.get_available_purchases()`}
diff --git a/packages/docs/src/pages/docs/apis/get-storefront.tsx b/packages/docs/src/pages/docs/apis/get-storefront.tsx
index 52c9640b..25b24ccc 100644
--- a/packages/docs/src/pages/docs/apis/get-storefront.tsx
+++ b/packages/docs/src/pages/docs/apis/get-storefront.tsx
@@ -124,7 +124,7 @@ function StorefrontBadge() {
{`using OpenIap;
using OpenIap.Maui;
-var countryCode = await ((QueryResolver)Iap.Instance).GetStorefrontAsync()`}
+var countryCode = await ((QueryResolver)OpenIapClient.Instance).GetStorefrontAsync()`}
),
gdscript: (
{`var country_code = await iap.get_storefront()`}
diff --git a/packages/docs/src/pages/docs/apis/has-active-subscriptions.tsx b/packages/docs/src/pages/docs/apis/has-active-subscriptions.tsx
index 9e228f9d..5020205a 100644
--- a/packages/docs/src/pages/docs/apis/has-active-subscriptions.tsx
+++ b/packages/docs/src/pages/docs/apis/has-active-subscriptions.tsx
@@ -137,7 +137,7 @@ function PremiumGate({ children }: { children: React.ReactNode }) {
{`using OpenIap;
using OpenIap.Maui;
-var isPremium = await ((QueryResolver)Iap.Instance).HasActiveSubscriptionsAsync()`}
+var isPremium = await ((QueryResolver)OpenIapClient.Instance).HasActiveSubscriptionsAsync()`}
),
gdscript: (
{`var is_premium = await iap.has_active_subscriptions()`}
diff --git a/packages/docs/src/pages/docs/apis/init-connection.tsx b/packages/docs/src/pages/docs/apis/init-connection.tsx
index b724f3db..10659b6a 100644
--- a/packages/docs/src/pages/docs/apis/init-connection.tsx
+++ b/packages/docs/src/pages/docs/apis/init-connection.tsx
@@ -196,10 +196,10 @@ kmpIAP.initConnection(
using OpenIap.Maui;
// Standard connection
-await ((MutationResolver)Iap.Instance).InitConnectionAsync();
+await ((MutationResolver)OpenIapClient.Instance).InitConnectionAsync();
// With alternative billing
-await ((MutationResolver)Iap.Instance).InitConnectionAsync(
+await ((MutationResolver)OpenIapClient.Instance).InitConnectionAsync(
new InitConnectionConfig
{
EnableBillingProgramAndroid = BillingProgramAndroid.UserChoiceBilling,
diff --git a/packages/docs/src/pages/docs/apis/ios/begin-refund-request-ios.tsx b/packages/docs/src/pages/docs/apis/ios/begin-refund-request-ios.tsx
index 34678e7e..503dad89 100644
--- a/packages/docs/src/pages/docs/apis/ios/begin-refund-request-ios.tsx
+++ b/packages/docs/src/pages/docs/apis/ios/begin-refund-request-ios.tsx
@@ -112,7 +112,7 @@ if (Platform.OS === 'ios') {
using OpenIap.Maui;
// iOS targets only — no-op on Android.
-var status = await ((MutationResolver)Iap.Instance)
+var status = await ((MutationResolver)OpenIapClient.Instance)
.BeginRefundRequestIOSAsync(sku: "com.app.premium");`}
),
gdscript: (
diff --git a/packages/docs/src/pages/docs/apis/ios/can-present-external-purchase-notice-ios.tsx b/packages/docs/src/pages/docs/apis/ios/can-present-external-purchase-notice-ios.tsx
index 1267b915..c366ac54 100644
--- a/packages/docs/src/pages/docs/apis/ios/can-present-external-purchase-notice-ios.tsx
+++ b/packages/docs/src/pages/docs/apis/ios/can-present-external-purchase-notice-ios.tsx
@@ -96,7 +96,7 @@ if (Platform.OS === 'ios') {
using OpenIap.Maui;
// kmp-iap (iOS targets only — no-op on Android)
-var can = await ((QueryResolver)Iap.Instance).CanPresentExternalPurchaseNoticeIOSAsync()`}
+var can = await ((QueryResolver)OpenIapClient.Instance).CanPresentExternalPurchaseNoticeIOSAsync()`}
),
gdscript: (
{`if iap.get_platform() == "iOS":
diff --git a/packages/docs/src/pages/docs/apis/ios/clear-transaction-ios.tsx b/packages/docs/src/pages/docs/apis/ios/clear-transaction-ios.tsx
index f507900c..3985b3ff 100644
--- a/packages/docs/src/pages/docs/apis/ios/clear-transaction-ios.tsx
+++ b/packages/docs/src/pages/docs/apis/ios/clear-transaction-ios.tsx
@@ -94,7 +94,7 @@ if (Platform.OS === 'ios') {
using OpenIap.Maui;
// kmp-iap (iOS targets only — no-op on Android)
-await ((MutationResolver)Iap.Instance).ClearTransactionIOSAsync();`}
+await ((MutationResolver)OpenIapClient.Instance).ClearTransactionIOSAsync();`}
),
gdscript: (
{`if iap.get_platform() == "iOS":
diff --git a/packages/docs/src/pages/docs/apis/ios/current-entitlement-ios.tsx b/packages/docs/src/pages/docs/apis/ios/current-entitlement-ios.tsx
index b9b3f757..2b4f7ace 100644
--- a/packages/docs/src/pages/docs/apis/ios/current-entitlement-ios.tsx
+++ b/packages/docs/src/pages/docs/apis/ios/current-entitlement-ios.tsx
@@ -111,7 +111,7 @@ if (Platform.OS === 'ios') {
using OpenIap.Maui;
// kmp-iap (iOS targets only — no-op on Android)
-var entitlement = await ((QueryResolver)Iap.Instance).CurrentEntitlementIOSAsync(sku: "com.app.premium")`}
+var entitlement = await ((QueryResolver)OpenIapClient.Instance).CurrentEntitlementIOSAsync(sku: "com.app.premium")`}
),
gdscript: (
{`if iap.get_platform() == "iOS":
diff --git a/packages/docs/src/pages/docs/apis/ios/get-all-transactions-ios.tsx b/packages/docs/src/pages/docs/apis/ios/get-all-transactions-ios.tsx
index ec829883..c70da567 100644
--- a/packages/docs/src/pages/docs/apis/ios/get-all-transactions-ios.tsx
+++ b/packages/docs/src/pages/docs/apis/ios/get-all-transactions-ios.tsx
@@ -105,7 +105,7 @@ if (Platform.OS === 'ios') {
using OpenIap.Maui;
// kmp-iap (iOS targets only — no-op on Android)
-var txs = await ((QueryResolver)Iap.Instance).GetAllTransactionsIOSAsync()`}
+var txs = await ((QueryResolver)OpenIapClient.Instance).GetAllTransactionsIOSAsync()`}
),
gdscript: (
{`if iap.get_platform() == "iOS":
diff --git a/packages/docs/src/pages/docs/apis/ios/get-app-transaction-ios.tsx b/packages/docs/src/pages/docs/apis/ios/get-app-transaction-ios.tsx
index b8be793c..7b6b5963 100644
--- a/packages/docs/src/pages/docs/apis/ios/get-app-transaction-ios.tsx
+++ b/packages/docs/src/pages/docs/apis/ios/get-app-transaction-ios.tsx
@@ -97,7 +97,7 @@ if (Platform.OS === 'ios') {
using OpenIap.Maui;
// kmp-iap (iOS targets only — no-op on Android)
-var appTx = await ((QueryResolver)Iap.Instance).GetAppTransactionIOSAsync()`}
+var appTx = await ((QueryResolver)OpenIapClient.Instance).GetAppTransactionIOSAsync()`}
),
gdscript: (
{`if iap.get_platform() == "iOS":
diff --git a/packages/docs/src/pages/docs/apis/ios/get-external-purchase-custom-link-token-ios.tsx b/packages/docs/src/pages/docs/apis/ios/get-external-purchase-custom-link-token-ios.tsx
index 7ecdfb51..7c5b0d79 100644
--- a/packages/docs/src/pages/docs/apis/ios/get-external-purchase-custom-link-token-ios.tsx
+++ b/packages/docs/src/pages/docs/apis/ios/get-external-purchase-custom-link-token-ios.tsx
@@ -140,7 +140,7 @@ if (Platform.OS === 'ios') {
using OpenIap.Maui;
// kmp-iap (iOS targets only — no-op on Android)
-var token = await ((QueryResolver)Iap.Instance).GetExternalPurchaseCustomLinkTokenIOSAsync(
+var token = await ((QueryResolver)OpenIapClient.Instance).GetExternalPurchaseCustomLinkTokenIOSAsync(
tokenType = ExternalPurchaseCustomLinkTokenTypeIOS.ACQUISITION
)`}
),
diff --git a/packages/docs/src/pages/docs/apis/ios/get-pending-transactions-ios.tsx b/packages/docs/src/pages/docs/apis/ios/get-pending-transactions-ios.tsx
index c6eab6b6..16b9e85f 100644
--- a/packages/docs/src/pages/docs/apis/ios/get-pending-transactions-ios.tsx
+++ b/packages/docs/src/pages/docs/apis/ios/get-pending-transactions-ios.tsx
@@ -100,7 +100,7 @@ if (Platform.OS === 'ios') {
using OpenIap.Maui;
// kmp-iap (iOS targets only — no-op on Android)
-var txs = await ((QueryResolver)Iap.Instance).GetPendingTransactionsIOSAsync()`}
+var txs = await ((QueryResolver)OpenIapClient.Instance).GetPendingTransactionsIOSAsync()`}
),
gdscript: (
{`if iap.get_platform() == "iOS":
diff --git a/packages/docs/src/pages/docs/apis/ios/get-promoted-product-ios.tsx b/packages/docs/src/pages/docs/apis/ios/get-promoted-product-ios.tsx
index 432f54b2..c4b3260a 100644
--- a/packages/docs/src/pages/docs/apis/ios/get-promoted-product-ios.tsx
+++ b/packages/docs/src/pages/docs/apis/ios/get-promoted-product-ios.tsx
@@ -98,7 +98,7 @@ if (Platform.OS === 'ios') {
using OpenIap.Maui;
// kmp-iap (iOS targets only — no-op on Android)
-var product = await ((QueryResolver)Iap.Instance).GetPromotedProductIOSAsync()`}
+var product = await ((QueryResolver)OpenIapClient.Instance).GetPromotedProductIOSAsync()`}
),
gdscript: (
{`if iap.get_platform() == "iOS":
diff --git a/packages/docs/src/pages/docs/apis/ios/get-receipt-data-ios.tsx b/packages/docs/src/pages/docs/apis/ios/get-receipt-data-ios.tsx
index 86cb9919..c0704820 100644
--- a/packages/docs/src/pages/docs/apis/ios/get-receipt-data-ios.tsx
+++ b/packages/docs/src/pages/docs/apis/ios/get-receipt-data-ios.tsx
@@ -95,7 +95,7 @@ if (Platform.OS === 'ios') {
using OpenIap.Maui;
// kmp-iap (iOS targets only — no-op on Android)
-var data = await ((QueryResolver)Iap.Instance).GetReceiptDataIOSAsync()`}
+var data = await ((QueryResolver)OpenIapClient.Instance).GetReceiptDataIOSAsync()`}
),
gdscript: (
{`if iap.get_platform() == "iOS":
diff --git a/packages/docs/src/pages/docs/apis/ios/get-storefront-ios.tsx b/packages/docs/src/pages/docs/apis/ios/get-storefront-ios.tsx
index a9d1f909..2936e88b 100644
--- a/packages/docs/src/pages/docs/apis/ios/get-storefront-ios.tsx
+++ b/packages/docs/src/pages/docs/apis/ios/get-storefront-ios.tsx
@@ -110,8 +110,8 @@ if (Platform.isIOS) {
using OpenIap.Maui;
// kmp-iap (iOS targets only — no-op on Android)
-// Deprecated — prefer await ((QueryResolver)Iap.Instance).GetStorefrontAsync()
-var code = await ((QueryResolver)Iap.Instance).GetStorefrontIOSAsync()`}
+// Deprecated — prefer await ((QueryResolver)OpenIapClient.Instance).GetStorefrontAsync()
+var code = await ((QueryResolver)OpenIapClient.Instance).GetStorefrontIOSAsync()`}
),
gdscript: (
{`if iap.get_platform() == "iOS":
diff --git a/packages/docs/src/pages/docs/apis/ios/get-transaction-jws-ios.tsx b/packages/docs/src/pages/docs/apis/ios/get-transaction-jws-ios.tsx
index 2c75cc7f..860306e3 100644
--- a/packages/docs/src/pages/docs/apis/ios/get-transaction-jws-ios.tsx
+++ b/packages/docs/src/pages/docs/apis/ios/get-transaction-jws-ios.tsx
@@ -108,7 +108,7 @@ if (Platform.OS === 'ios') {
using OpenIap.Maui;
// kmp-iap (iOS targets only — no-op on Android)
-var jws = await ((QueryResolver)Iap.Instance).GetTransactionJwsIOSAsync(sku: "com.app.premium")`}
+var jws = await ((QueryResolver)OpenIapClient.Instance).GetTransactionJwsIOSAsync(sku: "com.app.premium")`}
),
gdscript: (
{`if iap.get_platform() == "iOS":
diff --git a/packages/docs/src/pages/docs/apis/ios/is-eligible-for-external-purchase-custom-link-ios.tsx b/packages/docs/src/pages/docs/apis/ios/is-eligible-for-external-purchase-custom-link-ios.tsx
index 0e770ea5..bf40f967 100644
--- a/packages/docs/src/pages/docs/apis/ios/is-eligible-for-external-purchase-custom-link-ios.tsx
+++ b/packages/docs/src/pages/docs/apis/ios/is-eligible-for-external-purchase-custom-link-ios.tsx
@@ -106,7 +106,7 @@ if (Platform.OS === 'ios') {
using OpenIap.Maui;
// kmp-iap (iOS targets only — no-op on Android)
-var ok = await ((QueryResolver)Iap.Instance).IsEligibleForExternalPurchaseCustomLinkIOSAsync()`}
+var ok = await ((QueryResolver)OpenIapClient.Instance).IsEligibleForExternalPurchaseCustomLinkIOSAsync()`}
),
gdscript: (
{`if iap.get_platform() == "iOS":
diff --git a/packages/docs/src/pages/docs/apis/ios/is-eligible-for-intro-offer-ios.tsx b/packages/docs/src/pages/docs/apis/ios/is-eligible-for-intro-offer-ios.tsx
index ec265a01..6dab35ac 100644
--- a/packages/docs/src/pages/docs/apis/ios/is-eligible-for-intro-offer-ios.tsx
+++ b/packages/docs/src/pages/docs/apis/ios/is-eligible-for-intro-offer-ios.tsx
@@ -113,7 +113,7 @@ if (Platform.OS === 'ios') {
using OpenIap.Maui;
// kmp-iap (iOS targets only — no-op on Android)
-var ok = await ((QueryResolver)Iap.Instance).IsEligibleForIntroOfferIOSAsync(groupId: "com.app.subgroup")`}
+var ok = await ((QueryResolver)OpenIapClient.Instance).IsEligibleForIntroOfferIOSAsync(groupId: "com.app.subgroup")`}
),
gdscript: (
{`if iap.get_platform() == "iOS":
diff --git a/packages/docs/src/pages/docs/apis/ios/is-transaction-verified-ios.tsx b/packages/docs/src/pages/docs/apis/ios/is-transaction-verified-ios.tsx
index d0269ced..41030961 100644
--- a/packages/docs/src/pages/docs/apis/ios/is-transaction-verified-ios.tsx
+++ b/packages/docs/src/pages/docs/apis/ios/is-transaction-verified-ios.tsx
@@ -109,7 +109,7 @@ if (Platform.OS === 'ios') {
using OpenIap.Maui;
// kmp-iap (iOS targets only — no-op on Android)
-var ok = await ((QueryResolver)Iap.Instance).IsTransactionVerifiedIOSAsync(sku: "com.app.premium")`}
+var ok = await ((QueryResolver)OpenIapClient.Instance).IsTransactionVerifiedIOSAsync(sku: "com.app.premium")`}
),
gdscript: (
{`if iap.get_platform() == "iOS":
diff --git a/packages/docs/src/pages/docs/apis/ios/latest-transaction-ios.tsx b/packages/docs/src/pages/docs/apis/ios/latest-transaction-ios.tsx
index 6602103b..845ab07a 100644
--- a/packages/docs/src/pages/docs/apis/ios/latest-transaction-ios.tsx
+++ b/packages/docs/src/pages/docs/apis/ios/latest-transaction-ios.tsx
@@ -111,7 +111,7 @@ if (Platform.OS === 'ios') {
using OpenIap.Maui;
// kmp-iap (iOS targets only — no-op on Android)
-var tx = await ((QueryResolver)Iap.Instance).LatestTransactionIOSAsync(sku: "com.app.premium")`}
+var tx = await ((QueryResolver)OpenIapClient.Instance).LatestTransactionIOSAsync(sku: "com.app.premium")`}
),
gdscript: (
{`if iap.get_platform() == "iOS":
diff --git a/packages/docs/src/pages/docs/apis/ios/present-code-redemption-sheet-ios.tsx b/packages/docs/src/pages/docs/apis/ios/present-code-redemption-sheet-ios.tsx
index 31ca5652..ba9f9eac 100644
--- a/packages/docs/src/pages/docs/apis/ios/present-code-redemption-sheet-ios.tsx
+++ b/packages/docs/src/pages/docs/apis/ios/present-code-redemption-sheet-ios.tsx
@@ -96,7 +96,7 @@ if (Platform.OS === 'ios') {
using OpenIap.Maui;
// kmp-iap (iOS targets only — no-op on Android)
-await ((MutationResolver)Iap.Instance).PresentCodeRedemptionSheetIOSAsync();`}
+await ((MutationResolver)OpenIapClient.Instance).PresentCodeRedemptionSheetIOSAsync();`}
),
gdscript: (
{`if iap.get_platform() == "iOS":
diff --git a/packages/docs/src/pages/docs/apis/ios/present-external-purchase-link-ios.tsx b/packages/docs/src/pages/docs/apis/ios/present-external-purchase-link-ios.tsx
index c5cc9542..c492a531 100644
--- a/packages/docs/src/pages/docs/apis/ios/present-external-purchase-link-ios.tsx
+++ b/packages/docs/src/pages/docs/apis/ios/present-external-purchase-link-ios.tsx
@@ -120,7 +120,7 @@ if (Platform.OS === 'ios') {
using OpenIap.Maui;
// iOS targets only — no-op on Android
-var result = await ((MutationResolver)Iap.Instance).PresentExternalPurchaseLinkIOSAsync(
+var result = await ((MutationResolver)OpenIapClient.Instance).PresentExternalPurchaseLinkIOSAsync(
"https://yourstore.com/checkout");`}
),
gdscript: (
diff --git a/packages/docs/src/pages/docs/apis/ios/present-external-purchase-notice-sheet-ios.tsx b/packages/docs/src/pages/docs/apis/ios/present-external-purchase-notice-sheet-ios.tsx
index 4eddff12..30a606be 100644
--- a/packages/docs/src/pages/docs/apis/ios/present-external-purchase-notice-sheet-ios.tsx
+++ b/packages/docs/src/pages/docs/apis/ios/present-external-purchase-notice-sheet-ios.tsx
@@ -129,7 +129,7 @@ if (Platform.OS === 'ios') {
using OpenIap.Maui;
// kmp-iap (iOS targets only — no-op on Android)
-var result = await ((MutationResolver)Iap.Instance).PresentExternalPurchaseNoticeSheetIOSAsync();`}
+var result = await ((MutationResolver)OpenIapClient.Instance).PresentExternalPurchaseNoticeSheetIOSAsync();`}
),
gdscript: (
{`if iap.get_platform() == "iOS":
diff --git a/packages/docs/src/pages/docs/apis/ios/request-purchase-on-promoted-product-ios.tsx b/packages/docs/src/pages/docs/apis/ios/request-purchase-on-promoted-product-ios.tsx
index b85d6f43..d5f90c4f 100644
--- a/packages/docs/src/pages/docs/apis/ios/request-purchase-on-promoted-product-ios.tsx
+++ b/packages/docs/src/pages/docs/apis/ios/request-purchase-on-promoted-product-ios.tsx
@@ -115,7 +115,7 @@ using OpenIap.Maui;
// kmp-iap (iOS targets only — no-op on Android)
// Deprecated — prefer promotedProductListenerIOS + requestPurchase.
-await ((MutationResolver)Iap.Instance).RequestPurchaseOnPromotedProductIOSAsync();`}
+await ((MutationResolver)OpenIapClient.Instance).RequestPurchaseOnPromotedProductIOSAsync();`}
),
gdscript: (
{`if iap.get_platform() == "iOS":
diff --git a/packages/docs/src/pages/docs/apis/ios/show-external-purchase-custom-link-notice-ios.tsx b/packages/docs/src/pages/docs/apis/ios/show-external-purchase-custom-link-notice-ios.tsx
index fe6c466c..42a9787b 100644
--- a/packages/docs/src/pages/docs/apis/ios/show-external-purchase-custom-link-notice-ios.tsx
+++ b/packages/docs/src/pages/docs/apis/ios/show-external-purchase-custom-link-notice-ios.tsx
@@ -153,7 +153,7 @@ if (Platform.OS === 'ios') {
using OpenIap.Maui;
// kmp-iap (iOS targets only — no-op on Android)
-var result = await ((MutationResolver)Iap.Instance).ShowExternalPurchaseCustomLinkNoticeIOSAsync(
+var result = await ((MutationResolver)OpenIapClient.Instance).ShowExternalPurchaseCustomLinkNoticeIOSAsync(
ExternalPurchaseCustomLinkNoticeTypeIOS.Browser);`}
),
gdscript: (
diff --git a/packages/docs/src/pages/docs/apis/ios/show-manage-subscriptions-ios.tsx b/packages/docs/src/pages/docs/apis/ios/show-manage-subscriptions-ios.tsx
index ef5fdfb3..44b43ef1 100644
--- a/packages/docs/src/pages/docs/apis/ios/show-manage-subscriptions-ios.tsx
+++ b/packages/docs/src/pages/docs/apis/ios/show-manage-subscriptions-ios.tsx
@@ -107,7 +107,7 @@ if (Platform.OS === 'ios') {
using OpenIap.Maui;
// OpenIap.Maui (iOS targets only — no-op on Android)
-var changed = await ((MutationResolver)Iap.Instance).ShowManageSubscriptionsIOSAsync();`}
+var changed = await ((MutationResolver)OpenIapClient.Instance).ShowManageSubscriptionsIOSAsync();`}
),
gdscript: (
{`if iap.get_platform() == "iOS":
diff --git a/packages/docs/src/pages/docs/apis/ios/subscription-status-ios.tsx b/packages/docs/src/pages/docs/apis/ios/subscription-status-ios.tsx
index 5217f6fe..763458df 100644
--- a/packages/docs/src/pages/docs/apis/ios/subscription-status-ios.tsx
+++ b/packages/docs/src/pages/docs/apis/ios/subscription-status-ios.tsx
@@ -136,7 +136,7 @@ if (Platform.OS === 'ios') {
using OpenIap.Maui;
// kmp-iap (iOS targets only — no-op on Android)
-var status = await ((QueryResolver)Iap.Instance).SubscriptionStatusIOSAsync(sku: "com.app.monthly")`}
+var status = await ((QueryResolver)OpenIapClient.Instance).SubscriptionStatusIOSAsync(sku: "com.app.monthly")`}
),
gdscript: (
{`if iap.get_platform() == "iOS":
diff --git a/packages/docs/src/pages/docs/apis/ios/sync-ios.tsx b/packages/docs/src/pages/docs/apis/ios/sync-ios.tsx
index c0b446fd..e59238e1 100644
--- a/packages/docs/src/pages/docs/apis/ios/sync-ios.tsx
+++ b/packages/docs/src/pages/docs/apis/ios/sync-ios.tsx
@@ -93,7 +93,7 @@ if (Platform.OS === 'ios') {
using OpenIap.Maui;
// kmp-iap (iOS targets only — no-op on Android)
-await ((MutationResolver)Iap.Instance).SyncIOSAsync();`}
+await ((MutationResolver)OpenIapClient.Instance).SyncIOSAsync();`}
),
gdscript: (
{`if iap.get_platform() == "iOS":
diff --git a/packages/docs/src/pages/docs/apis/ios/validate-receipt-ios.tsx b/packages/docs/src/pages/docs/apis/ios/validate-receipt-ios.tsx
index db5b11e4..2ae2162c 100644
--- a/packages/docs/src/pages/docs/apis/ios/validate-receipt-ios.tsx
+++ b/packages/docs/src/pages/docs/apis/ios/validate-receipt-ios.tsx
@@ -133,7 +133,7 @@ using OpenIap.Maui;
// kmp-iap (iOS targets only — no-op on Android)
// Deprecated — prefer verifyPurchase().
-await ((MutationResolver)Iap.Instance).ValidateReceiptIOSAsync(
+await ((MutationResolver)OpenIapClient.Instance).ValidateReceiptIOSAsync(
new VerifyPurchaseProps
{
Apple = new VerifyPurchaseAppleOptions
diff --git a/packages/docs/src/pages/docs/apis/request-purchase.tsx b/packages/docs/src/pages/docs/apis/request-purchase.tsx
index e610a4da..13140249 100644
--- a/packages/docs/src/pages/docs/apis/request-purchase.tsx
+++ b/packages/docs/src/pages/docs/apis/request-purchase.tsx
@@ -112,7 +112,7 @@ type RequestPurchaseProps =
csharp: (
{`Task RequestPurchaseAsync(RequestPurchaseProps props);
-// Result is event-based — listen via Iap.Instance.PurchaseUpdated /
+// Result is event-based — listen via OpenIapClient.Instance.PurchaseUpdated /
// PurchaseError. The returned RequestPurchaseResult is for legacy consumers.`}
),
}}
@@ -374,20 +374,20 @@ await iap.request_purchase(props)`}
using OpenIap.Maui;
// Subscribe to results FIRST — requestPurchase is event-based.
-Iap.Instance.PurchaseUpdated.Subscribe(async purchase => {
+OpenIapClient.Instance.PurchaseUpdated.Subscribe(async purchase => {
// 1. Validate on your server, 2. Grant entitlement,
// 3. Finish transaction (Android auto-refunds after 3 days otherwise!)
- await ((MutationResolver)Iap.Instance).FinishTransactionAsync(
+ await ((MutationResolver)OpenIapClient.Instance).FinishTransactionAsync(
purchase: new PurchaseInput(purchase),
isConsumable: true);
});
-Iap.Instance.PurchaseError.Subscribe(error => {
+OpenIapClient.Instance.PurchaseError.Subscribe(error => {
Console.WriteLine($"{error.Code}: {error.Message}");
});
// Then request the purchase
-await ((MutationResolver)Iap.Instance).RequestPurchaseAsync(new RequestPurchaseProps {
+await ((MutationResolver)OpenIapClient.Instance).RequestPurchaseAsync(new RequestPurchaseProps {
RequestPurchase = new RequestPurchasePropsByPlatforms {
Apple = new RequestPurchaseIosProps { Sku = "com.app.premium" },
Google = new RequestPurchaseAndroidProps { Skus = new[] { "com.app.premium" } },
diff --git a/packages/docs/src/pages/docs/apis/restore-purchases.tsx b/packages/docs/src/pages/docs/apis/restore-purchases.tsx
index 935c1009..e7a8f07e 100644
--- a/packages/docs/src/pages/docs/apis/restore-purchases.tsx
+++ b/packages/docs/src/pages/docs/apis/restore-purchases.tsx
@@ -177,7 +177,7 @@ function RestoreButton() {
{`using OpenIap;
using OpenIap.Maui;
-await ((MutationResolver)Iap.Instance).RestorePurchasesAsync();`}
+await ((MutationResolver)OpenIapClient.Instance).RestorePurchasesAsync();`}
),
gdscript: (
{`await iap.restore_purchases()`}
diff --git a/packages/docs/src/pages/docs/events/android/developer-provided-billing-listener-android.tsx b/packages/docs/src/pages/docs/events/android/developer-provided-billing-listener-android.tsx
index 8d788193..01b94694 100644
--- a/packages/docs/src/pages/docs/events/android/developer-provided-billing-listener-android.tsx
+++ b/packages/docs/src/pages/docs/events/android/developer-provided-billing-listener-android.tsx
@@ -60,7 +60,7 @@ fun addDeveloperProvidedBillingListener(
using OpenIap.Maui;
// Observable callback approach.
-IDisposable subscription = Iap.Instance.DeveloperProvidedBillingAndroid.Subscribe(details =>
+IDisposable subscription = OpenIapClient.Instance.DeveloperProvidedBillingAndroid.Subscribe(details =>
{
Console.WriteLine(details.ExternalTransactionToken);
});`}
@@ -172,7 +172,7 @@ subscription.cancel();`}
{`using OpenIap;
using OpenIap.Maui;
-var subscription = Iap.Instance.DeveloperProvidedBillingAndroid.Subscribe(async details =>
+var subscription = OpenIapClient.Instance.DeveloperProvidedBillingAndroid.Subscribe(async details =>
{
Console.WriteLine("User selected developer billing");
Console.WriteLine($"Token: {details.ExternalTransactionToken}");
diff --git a/packages/docs/src/pages/docs/events/android/user-choice-billing-listener-android.tsx b/packages/docs/src/pages/docs/events/android/user-choice-billing-listener-android.tsx
index f4483832..97e028cb 100644
--- a/packages/docs/src/pages/docs/events/android/user-choice-billing-listener-android.tsx
+++ b/packages/docs/src/pages/docs/events/android/user-choice-billing-listener-android.tsx
@@ -174,7 +174,7 @@ subscription.cancel();`}
using OpenIap.Maui;
using System;
-using var subscription = Iap.Instance.UserChoiceBillingAndroid.Subscribe(details =>
+using var subscription = OpenIapClient.Instance.UserChoiceBillingAndroid.Subscribe(details =>
{
Console.WriteLine("User chose alternative billing");
Console.WriteLine($"Products: {string.Join(", ", details.Products)}");
diff --git a/packages/docs/src/pages/docs/events/ios/promoted-product-listener-ios.tsx b/packages/docs/src/pages/docs/events/ios/promoted-product-listener-ios.tsx
index a986ed34..52ec7a23 100644
--- a/packages/docs/src/pages/docs/events/ios/promoted-product-listener-ios.tsx
+++ b/packages/docs/src/pages/docs/events/ios/promoted-product-listener-ios.tsx
@@ -163,7 +163,7 @@ final subscription = FlutterInappPurchase.promotedProductIOS.listen((productId)
subscription.cancel();`}
),
csharp: (
- {`// .NET MAUI — see OpenIap.Maui.Iap.Instance.
+ {`// .NET MAUI — see OpenIap.Maui.OpenIapClient.Instance.
// The full operation surface lives on OpenIap.QueryResolver /
// MutationResolver / SubscriptionResolver (auto-generated from the schema).`}
),
diff --git a/packages/docs/src/pages/docs/events/purchase-error-listener.tsx b/packages/docs/src/pages/docs/events/purchase-error-listener.tsx
index bef43149..ab36d25a 100644
--- a/packages/docs/src/pages/docs/events/purchase-error-listener.tsx
+++ b/packages/docs/src/pages/docs/events/purchase-error-listener.tsx
@@ -49,7 +49,7 @@ val purchaseErrors: Flow`}
using OpenIap.Maui;
using System;
-IObservable purchaseErrors = Iap.Instance.PurchaseError;`}
+IObservable purchaseErrors = OpenIapClient.Instance.PurchaseError;`}
),
}}
@@ -211,7 +211,7 @@ subscription.cancel();`}
{`using OpenIap;
using OpenIap.Maui;
-var subscription = Iap.Instance.PurchaseError.Subscribe(async error =>
+var subscription = OpenIapClient.Instance.PurchaseError.Subscribe(async error =>
{
Console.WriteLine($"Purchase error: {error.Code} - {error.Message}");
@@ -222,7 +222,7 @@ var subscription = Iap.Instance.PurchaseError.Subscribe(async error =>
break;
case ErrorCode.AlreadyOwned:
// Restore purchases instead.
- await ((MutationResolver)Iap.Instance).RestorePurchasesAsync();
+ await ((MutationResolver)OpenIapClient.Instance).RestorePurchasesAsync();
break;
case ErrorCode.NetworkError:
ShowRetryDialog();
diff --git a/packages/docs/src/pages/docs/events/purchase-updated-listener.tsx b/packages/docs/src/pages/docs/events/purchase-updated-listener.tsx
index e5649f19..f3c73a6d 100644
--- a/packages/docs/src/pages/docs/events/purchase-updated-listener.tsx
+++ b/packages/docs/src/pages/docs/events/purchase-updated-listener.tsx
@@ -70,7 +70,7 @@ val purchaseUpdates: Flow = kmpIAP.purchaseUpdatedListener`} purchaseUpdates = Iap.Instance.PurchaseUpdated;`}
+IObservable purchaseUpdates = OpenIapClient.Instance.PurchaseUpdated;`}
),
gdscript: (
{`signal purchase_updated(purchase: Purchase)`}
@@ -116,7 +116,7 @@ IObservable purchaseUpdates = Iap.Instance.PurchaseUpdated;`}
),
csharp: (
- {`var updates = Iap.Instance.PurchaseUpdatedWithOptions(
+ {`var updates = OpenIapClient.Instance.PurchaseUpdatedWithOptions(
new PurchaseUpdatedListenerOptions
{
DedupeTransactionIOS = false,
@@ -247,7 +247,7 @@ subscription.cancel();`}{`using OpenIap;
using OpenIap.Maui;
-var subscription = Iap.Instance.PurchaseUpdated.Subscribe(async purchase =>
+var subscription = OpenIapClient.Instance.PurchaseUpdated.Subscribe(async purchase =>
{
if (purchase is PurchaseCommon purchaseInfo)
{
@@ -262,7 +262,7 @@ var subscription = Iap.Instance.PurchaseUpdated.Subscribe(async purchase =>
await DeliverProductAsync(validPurchase.ProductId);
}
- await ((MutationResolver)Iap.Instance).FinishTransactionAsync(
+ await ((MutationResolver)OpenIapClient.Instance).FinishTransactionAsync(
new PurchaseInput(purchase),
isConsumable: false);
}
diff --git a/packages/docs/src/pages/docs/events/subscription-billing-issue-listener.tsx b/packages/docs/src/pages/docs/events/subscription-billing-issue-listener.tsx
index 6e78d392..7380fc86 100644
--- a/packages/docs/src/pages/docs/events/subscription-billing-issue-listener.tsx
+++ b/packages/docs/src/pages/docs/events/subscription-billing-issue-listener.tsx
@@ -61,7 +61,7 @@ val subscriptionBillingIssueListener: Flow`}
using OpenIap.Maui;
// Observable callback approach (iOS 18+ / Play Billing 8.1+).
-IDisposable subscription = Iap.Instance.SubscriptionBillingIssue.Subscribe(purchase =>
+IDisposable subscription = OpenIapClient.Instance.SubscriptionBillingIssue.Subscribe(purchase =>
{
Console.WriteLine("Subscription billing issue received");
});`}
@@ -172,7 +172,7 @@ subscription.cancel();`}
using OpenIap.Maui;
// iOS 18+ / Play Billing Library 8.1+
-var subscription = Iap.Instance.SubscriptionBillingIssue.Subscribe(purchase =>
+var subscription = OpenIapClient.Instance.SubscriptionBillingIssue.Subscribe(purchase =>
{
if (purchase is PurchaseCommon purchaseInfo)
{
diff --git a/packages/docs/src/pages/docs/features/discount.tsx b/packages/docs/src/pages/docs/features/discount.tsx
index af3e010f..eec51289 100644
--- a/packages/docs/src/pages/docs/features/discount.tsx
+++ b/packages/docs/src/pages/docs/features/discount.tsx
@@ -443,7 +443,7 @@ using System;
using OpenIap.Maui;
using System.Linq;
-var result = await ((QueryResolver)Iap.Instance).FetchProductsAsync(new ProductRequest
+var result = await ((QueryResolver)OpenIapClient.Instance).FetchProductsAsync(new ProductRequest
{
Skus = new[] { "premium_feature", "coins_100" },
Type = ProductQueryType.InApp,
@@ -853,7 +853,7 @@ ProductCardViewModel BuildProductCard(ProductAndroid product)
async Task PurchaseAsync(ProductAndroid product)
{
- await ((MutationResolver)Iap.Instance).RequestPurchaseAsync(new RequestPurchaseProps
+ await ((MutationResolver)OpenIapClient.Instance).RequestPurchaseAsync(new RequestPurchaseProps
{
RequestPurchase = new RequestPurchasePropsByPlatforms
{
@@ -1371,7 +1371,7 @@ async Task PurchaseWithOfferAsync(ProductAndroid product, int offerIndex = 0)
var selectedOffer = offers.ElementAtOrDefault(offerIndex)
?? throw new ArgumentOutOfRangeException(nameof(offerIndex));
- await ((MutationResolver)Iap.Instance).RequestPurchaseAsync(new RequestPurchaseProps
+ await ((MutationResolver)OpenIapClient.Instance).RequestPurchaseAsync(new RequestPurchaseProps
{
Type = ProductQueryType.InApp,
RequestPurchase = new RequestPurchasePropsByPlatforms
diff --git a/packages/docs/src/pages/docs/features/refund.tsx b/packages/docs/src/pages/docs/features/refund.tsx
index 6b92a8a3..89c42284 100644
--- a/packages/docs/src/pages/docs/features/refund.tsx
+++ b/packages/docs/src/pages/docs/features/refund.tsx
@@ -205,7 +205,7 @@ switch (status) {
{`using OpenIap;
using OpenIap.Maui;
-var status = await ((MutationResolver)Iap.Instance)
+var status = await ((MutationResolver)OpenIapClient.Instance)
.BeginRefundRequestIOSAsync(purchase.ProductId);
switch (status)
diff --git a/packages/docs/src/pages/docs/features/subscription-billing-issue.tsx b/packages/docs/src/pages/docs/features/subscription-billing-issue.tsx
index e576c4f2..cbb1d898 100644
--- a/packages/docs/src/pages/docs/features/subscription-billing-issue.tsx
+++ b/packages/docs/src/pages/docs/features/subscription-billing-issue.tsx
@@ -183,7 +183,7 @@ final sub = iap.subscriptionBillingIssueListener.listen((purchase) {
await sub.cancel();`}
),
csharp: (
- {`// .NET MAUI — see OpenIap.Maui.Iap.Instance.
+ {`// .NET MAUI — see OpenIap.Maui.OpenIapClient.Instance.
// The full operation surface lives on OpenIap.QueryResolver /
// MutationResolver / SubscriptionResolver (auto-generated from the schema).`}
),
diff --git a/packages/docs/src/pages/docs/features/subscription/index.tsx b/packages/docs/src/pages/docs/features/subscription/index.tsx
index 2cc14b08..d09d1dc7 100644
--- a/packages/docs/src/pages/docs/features/subscription/index.tsx
+++ b/packages/docs/src/pages/docs/features/subscription/index.tsx
@@ -256,7 +256,7 @@ using System;
using System.Linq;
// Fetch subscription products
-var result = await ((QueryResolver)Iap.Instance).FetchProductsAsync(new ProductRequest
+var result = await ((QueryResolver)OpenIapClient.Instance).FetchProductsAsync(new ProductRequest
{
Skus = new[] { "premium_monthly" },
Type = ProductQueryType.Subs,
@@ -454,7 +454,7 @@ string? DisplayIntroOffer(ProductSubscriptionIOS subscription)
}
// Check eligibility
-var isEligible = await ((QueryResolver)Iap.Instance)
+var isEligible = await ((QueryResolver)OpenIapClient.Instance)
.IsEligibleForIntroOfferIOSAsync("premium_monthly");
if (isEligible && subscription is not null)
@@ -695,7 +695,7 @@ async Task PurchaseWithPromoOfferAsync(string subscriptionId, string offerId)
timestamp: timestamp);
// 2. Purchase with the promotional offer
- await ((MutationResolver)Iap.Instance).RequestPurchaseAsync(new RequestPurchaseProps
+ await ((MutationResolver)OpenIapClient.Instance).RequestPurchaseAsync(new RequestPurchaseProps
{
Type = ProductQueryType.Subs,
RequestSubscription = new RequestSubscriptionPropsByPlatforms
@@ -837,7 +837,7 @@ using OpenIap.Maui;
async Task PurchaseSubscriptionAsync(string subscriptionId)
{
// Intro offer is applied automatically when eligible.
- await ((MutationResolver)Iap.Instance).RequestPurchaseAsync(new RequestPurchaseProps
+ await ((MutationResolver)OpenIapClient.Instance).RequestPurchaseAsync(new RequestPurchaseProps
{
Type = ProductQueryType.Subs,
RequestSubscription = new RequestSubscriptionPropsByPlatforms
@@ -1163,7 +1163,7 @@ using System;
using System.Linq;
// Fetch subscription products
-var result = await ((QueryResolver)Iap.Instance).FetchProductsAsync(new ProductRequest
+var result = await ((QueryResolver)OpenIapClient.Instance).FetchProductsAsync(new ProductRequest
{
Skus = new[] { "premium_monthly" },
Type = ProductQueryType.Subs,
@@ -1404,7 +1404,7 @@ async Task PurchaseSubscriptionAsync(string subscriptionId, ProductSubscriptionA
return;
}
- await ((MutationResolver)Iap.Instance).RequestPurchaseAsync(new RequestPurchaseProps
+ await ((MutationResolver)OpenIapClient.Instance).RequestPurchaseAsync(new RequestPurchaseProps
{
Type = ProductQueryType.Subs,
RequestSubscription = new RequestSubscriptionPropsByPlatforms
@@ -1755,7 +1755,7 @@ async Task HandlePurchaseAsync(
// Store it before purchase.
purchasedBasePlanId = basePlanId;
- await ((MutationResolver)Iap.Instance).RequestPurchaseAsync(new RequestPurchaseProps
+ await ((MutationResolver)OpenIapClient.Instance).RequestPurchaseAsync(new RequestPurchaseProps
{
Type = ProductQueryType.Subs,
RequestSubscription = new RequestSubscriptionPropsByPlatforms
@@ -2202,7 +2202,7 @@ async Task PurchaseWithOfferAsync(
return;
}
- await ((MutationResolver)Iap.Instance).RequestPurchaseAsync(new RequestPurchaseProps
+ await ((MutationResolver)OpenIapClient.Instance).RequestPurchaseAsync(new RequestPurchaseProps
{
Type = ProductQueryType.Subs,
RequestSubscription = new RequestSubscriptionPropsByPlatforms
@@ -2750,7 +2750,7 @@ using OpenIap.Maui;
using System;
// Check if user has any active subscription
-var hasActive = await ((QueryResolver)Iap.Instance).HasActiveSubscriptionsAsync();
+var hasActive = await ((QueryResolver)OpenIapClient.Instance).HasActiveSubscriptionsAsync();
if (hasActive)
{
Console.WriteLine("User has premium access");
@@ -2758,7 +2758,7 @@ if (hasActive)
// Get all active subscriptions
var activeSubscriptions =
- await ((QueryResolver)Iap.Instance).GetActiveSubscriptionsAsync();
+ await ((QueryResolver)OpenIapClient.Instance).GetActiveSubscriptionsAsync();
foreach (var subscription in activeSubscriptions)
{
@@ -3193,7 +3193,7 @@ using System;
// Note: subscriptionStatusIOS is iOS-only.
async Task<(bool IsActive, string Status)> CheckSubscriptionStatusAsync(string sku)
{
- var statuses = await ((QueryResolver)Iap.Instance).SubscriptionStatusIOSAsync(sku);
+ var statuses = await ((QueryResolver)OpenIapClient.Instance).SubscriptionStatusIOSAsync(sku);
foreach (var status in statuses)
{
@@ -3223,7 +3223,7 @@ async Task<(bool IsActive, string Status)> CheckSubscriptionStatusAsync(string s
// Using ActiveSubscription for quick checks.
async Task CheckFromActiveSubscriptionsAsync()
{
- var subscriptions = await ((QueryResolver)Iap.Instance).GetActiveSubscriptionsAsync();
+ var subscriptions = await ((QueryResolver)OpenIapClient.Instance).GetActiveSubscriptionsAsync();
var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
foreach (var subscription in subscriptions)
@@ -3560,7 +3560,7 @@ public sealed record SubscriptionAccessResult(
// Client-side: Can only check if purchase exists.
async Task CheckAndroidSubscriptionAsync(string subscriptionId)
{
- var purchases = await ((QueryResolver)Iap.Instance).GetAvailablePurchasesAsync();
+ var purchases = await ((QueryResolver)OpenIapClient.Instance).GetAvailablePurchasesAsync();
var purchase = purchases
.OfType()
.FirstOrDefault(purchase => purchase.ProductId == subscriptionId);
@@ -3822,7 +3822,7 @@ Future manageSubscriptions() async {
using OpenIap.Maui;
// Open subscription management page.
-await ((MutationResolver)Iap.Instance).DeepLinkToSubscriptionsAsync();`}
+await ((MutationResolver)OpenIapClient.Instance).DeepLinkToSubscriptionsAsync();`}
),
gdscript: (
{`# Open subscription management page
diff --git a/packages/docs/src/pages/docs/features/subscription/upgrade-downgrade.tsx b/packages/docs/src/pages/docs/features/subscription/upgrade-downgrade.tsx
index ffa91395..bc893e70 100644
--- a/packages/docs/src/pages/docs/features/subscription/upgrade-downgrade.tsx
+++ b/packages/docs/src/pages/docs/features/subscription/upgrade-downgrade.tsx
@@ -359,7 +359,7 @@ for (final sub in subscriptions) {
using OpenIap.Maui;
// Detecting upgrades with pendingUpgradeProductId.
-var subscriptions = await ((QueryResolver)Iap.Instance).GetActiveSubscriptionsAsync();
+var subscriptions = await ((QueryResolver)OpenIapClient.Instance).GetActiveSubscriptionsAsync();
foreach (var subscription in subscriptions)
{
@@ -532,7 +532,7 @@ for (final sub in subscriptions) {
using OpenIap.Maui;
// Detecting downgrades.
-var subscriptions = await ((QueryResolver)Iap.Instance).GetActiveSubscriptionsAsync();
+var subscriptions = await ((QueryResolver)OpenIapClient.Instance).GetActiveSubscriptionsAsync();
foreach (var subscription in subscriptions)
{
@@ -694,7 +694,7 @@ for (final sub in subscriptions) {
{`using OpenIap;
using OpenIap.Maui;
-var subscriptions = await ((QueryResolver)Iap.Instance).GetActiveSubscriptionsAsync();
+var subscriptions = await ((QueryResolver)OpenIapClient.Instance).GetActiveSubscriptionsAsync();
foreach (var subscription in subscriptions)
{
@@ -1119,7 +1119,7 @@ using OpenIap.Maui;
// Complete example: build a subscription status message.
async Task GetSubscriptionStatusMessageAsync()
{
- var subscriptions = await ((QueryResolver)Iap.Instance).GetActiveSubscriptionsAsync();
+ var subscriptions = await ((QueryResolver)OpenIapClient.Instance).GetActiveSubscriptionsAsync();
var subscription = subscriptions.FirstOrDefault();
if (subscription is null)
@@ -1660,7 +1660,7 @@ using System.Linq;
// Android upgrade with proration
// Get current subscription
-var purchases = await ((QueryResolver)Iap.Instance).GetAvailablePurchasesAsync();
+var purchases = await ((QueryResolver)OpenIapClient.Instance).GetAvailablePurchasesAsync();
var currentSub = purchases
.OfType()
.FirstOrDefault(purchase => purchase.ProductId == "basic_monthly");
@@ -1668,7 +1668,7 @@ var currentSub = purchases
if (currentSub is not null)
{
// Upgrade to premium with time proration
- await ((MutationResolver)Iap.Instance).RequestPurchaseAsync(new RequestPurchaseProps
+ await ((MutationResolver)OpenIapClient.Instance).RequestPurchaseAsync(new RequestPurchaseProps
{
Type = ProductQueryType.Subs,
RequestSubscription = new RequestSubscriptionPropsByPlatforms
@@ -1887,7 +1887,7 @@ using System.Linq;
// Android downgrade with deferred replacement
// Get current subscription
-var purchases = await ((QueryResolver)Iap.Instance).GetAvailablePurchasesAsync();
+var purchases = await ((QueryResolver)OpenIapClient.Instance).GetAvailablePurchasesAsync();
var premiumPurchase = purchases
.OfType()
.FirstOrDefault(purchase => purchase.ProductId == "premium_monthly");
@@ -1895,7 +1895,7 @@ var premiumPurchase = purchases
if (premiumPurchase is not null)
{
// Downgrade - takes effect at next billing cycle
- await ((MutationResolver)Iap.Instance).RequestPurchaseAsync(new RequestPurchaseProps
+ await ((MutationResolver)OpenIapClient.Instance).RequestPurchaseAsync(new RequestPurchaseProps
{
Type = ProductQueryType.Subs,
RequestSubscription = new RequestSubscriptionPropsByPlatforms
@@ -2082,7 +2082,7 @@ using System.Linq;
// Android subscription replacement with 8.1.0+ API
// Get current subscription
-var purchases = await ((QueryResolver)Iap.Instance).GetAvailablePurchasesAsync();
+var purchases = await ((QueryResolver)OpenIapClient.Instance).GetAvailablePurchasesAsync();
var currentSub = purchases
.OfType()
.FirstOrDefault(purchase => purchase.ProductId == "premium_monthly");
@@ -2090,7 +2090,7 @@ var currentSub = purchases
if (currentSub is not null)
{
// Upgrade using the new per-product replacement params
- await ((MutationResolver)Iap.Instance).RequestPurchaseAsync(new RequestPurchaseProps
+ await ((MutationResolver)OpenIapClient.Instance).RequestPurchaseAsync(new RequestPurchaseProps
{
Type = ProductQueryType.Subs,
RequestSubscription = new RequestSubscriptionPropsByPlatforms
@@ -2225,7 +2225,7 @@ using System.Linq;
// Android - check if subscription will change
-var purchases = await ((QueryResolver)Iap.Instance).GetAvailablePurchasesAsync();
+var purchases = await ((QueryResolver)OpenIapClient.Instance).GetAvailablePurchasesAsync();
foreach (var purchase in purchases.OfType())
{
@@ -2539,7 +2539,7 @@ using System.Linq;
async Task ChangeSubscriptionAsync(string newSku, bool isUpgrade)
{
// Get current subscription
- var purchases = await ((QueryResolver)Iap.Instance).GetAvailablePurchasesAsync();
+ var purchases = await ((QueryResolver)OpenIapClient.Instance).GetAvailablePurchasesAsync();
var currentSub = purchases
.OfType()
.FirstOrDefault(purchase =>
@@ -2558,7 +2558,7 @@ async Task ChangeSubscriptionAsync(string newSku, bool isUpgrade)
try
{
- await ((MutationResolver)Iap.Instance).RequestPurchaseAsync(new RequestPurchaseProps
+ await ((MutationResolver)OpenIapClient.Instance).RequestPurchaseAsync(new RequestPurchaseProps
{
Type = ProductQueryType.Subs,
RequestSubscription = new RequestSubscriptionPropsByPlatforms
diff --git a/packages/docs/src/pages/docs/features/validation.tsx b/packages/docs/src/pages/docs/features/validation.tsx
index c07a999c..b394e7b0 100644
--- a/packages/docs/src/pages/docs/features/validation.tsx
+++ b/packages/docs/src/pages/docs/features/validation.tsx
@@ -133,7 +133,7 @@ if (result.isValid) {
{`using OpenIap;
using OpenIap.Maui;
-var result = await ((MutationResolver)Iap.Instance).VerifyPurchaseAsync(
+var result = await ((MutationResolver)OpenIapClient.Instance).VerifyPurchaseAsync(
new VerifyPurchaseProps
{
Google = new VerifyPurchaseGoogleOptions
diff --git a/packages/docs/src/pages/docs/getting-started.tsx b/packages/docs/src/pages/docs/getting-started.tsx
index 60ddbc1a..1e15802a 100644
--- a/packages/docs/src/pages/docs/getting-started.tsx
+++ b/packages/docs/src/pages/docs/getting-started.tsx
@@ -307,8 +307,8 @@ await iap.requestPurchaseWithBuilder(
{`using OpenIap;
using OpenIap.Maui;
-var query = (QueryResolver)Iap.Instance;
-var mutation = (MutationResolver)Iap.Instance;
+var query = (QueryResolver)OpenIapClient.Instance;
+var mutation = (MutationResolver)OpenIapClient.Instance;
// 1. Open the store connection on app start.
await mutation.InitConnectionAsync();
@@ -321,7 +321,7 @@ var products = await query.FetchProductsAsync(new ProductRequest
});
// 3. Listen for purchase results.
-var subscription = Iap.Instance.PurchaseUpdated.Subscribe(async purchase =>
+var subscription = OpenIapClient.Instance.PurchaseUpdated.Subscribe(async purchase =>
{
// Verify on your backend, grant entitlement, then finish.
await mutation.FinishTransactionAsync(
diff --git a/packages/docs/src/pages/docs/kit-backend.tsx b/packages/docs/src/pages/docs/kit-backend.tsx
index 615e8f37..88c40c4c 100644
--- a/packages/docs/src/pages/docs/kit-backend.tsx
+++ b/packages/docs/src/pages/docs/kit-backend.tsx
@@ -181,7 +181,7 @@ if (active) {
using OpenIap.Maui;
var openiapProjectKey = "";
-var api = Iap.KitApi(new KitApiOptions { ApiKey = openiapProjectKey });
+var api = OpenIapClient.KitApi(new KitApiOptions { ApiKey = openiapProjectKey });
var status = await api.StatusAsync("user-1");
if (status.Active)
{
diff --git a/packages/docs/src/pages/docs/setup/maui.tsx b/packages/docs/src/pages/docs/setup/maui.tsx
index 14e99987..6f200cd2 100644
--- a/packages/docs/src/pages/docs/setup/maui.tsx
+++ b/packages/docs/src/pages/docs/setup/maui.tsx
@@ -22,10 +22,11 @@ function MauiSetup() {
Package shape: apps reference only{' '}
- OpenIap.Maui. The Android binding, iOS binding, Google
- Play Billing AARs, and StoreKit xcframework resources are flattened
- into that package, so NuGet consumers do not add separate binding
- packages or NativeReference entries.
+ OpenIap.Maui. OpenIAP-owned Android and iOS binding
+ outputs are flattened into that package. Google Billing, Play
+ Services, Gson, AndroidX, and Kotlin Android libraries stay as normal
+ NuGet dependencies so your app can deduplicate them with its own
+ package graph.
@@ -61,7 +62,7 @@ function MauiSetup() {
.NET
- .NET 9 SDK and the MAUI workload:{' '}
+ .NET 9 or .NET 10 SDK and the MAUI workload:{' '}
dotnet workload install maui
@@ -150,7 +151,11 @@ function MauiSetup() {
TargetFrameworks:
- Use Iap.Instance as the entry point. Cast it to the
- generated resolver interfaces when calling query or mutation APIs.
+ Use OpenIapClient.Instance as the entry point. Cast it to
+ the generated resolver interfaces when calling query or mutation APIs.