Use this page with AI Copy this into your coding assistant and add your request.
Read https://openiap.dev/docs/apis/request-purchase and https://openiap.dev/llms.txt. Follow the reading instructions, detailed reference, and linked guides relevant to my task before making changes.
Inspect my existing project and reuse its framework and conventions. Ask me for missing product decisions. Implement the requested behavior and run the applicable checks.
Show the working result, the commands and actual test results, and any remaining limitations. Keep your explanation brief.
My request: [describe what customers should be able to do]See an example request → requestPurchase Initiate a purchase flow. The result is delivered through purchaseUpdatedListener, not the return value.
iOS: Calls Product.purchase(options:) and emits the result on the Transaction.updates listener — the return value is just the dispatch ack. Apple docs . Android: Calls BillingClient.launchBillingFlow and emits the result on PurchasesUpdatedListener. Subscription offers require an offerToken. Google docs .
Important
APIs starting with request are event-based operations, not promise-based.
While these APIs return values for various purposes, you should not rely on their return values for actual purchase results . Instead, listen for events through purchaseUpdatedListener or purchaseErrorListener .
This is because Apple's purchase system is fundamentally event-based, not promise-based. For more details, see this issue comment .
The request prefix indicates that these are event requests — use the appropriate listeners to handle the actual results.
Signature func requestPurchase(_ params: RequestPurchaseProps) async throws -> RequestPurchaseResult?Kotlin example suspend fun requestPurchase(params: RequestPurchaseProps): RequestPurchaseResult?TypeScript example requestPurchase(props: RequestPurchaseProps): Promise<Purchase | void>
type RequestPurchaseProps =
| { request: RequestPurchasePropsByPlatforms; type: 'in-app' }
| { request: RequestSubscriptionPropsByPlatforms; type: 'subs' }Dart example Future<RequestPurchaseResult?> requestPurchase(RequestPurchaseProps params);C# (MAUI) example Task<RequestPurchaseResult?> RequestPurchaseAsync(RequestPurchaseProps @params);
// Result is event-based — listen via OpenIapClient.Instance.PurchaseUpdated /
// PurchaseError. The return value describes the request dispatch result.Kotlin (KMP) example suspend fun requestPurchase(params: RequestPurchaseProps): RequestPurchaseResult?GDScript example func request_purchase(props: RequestPurchaseProps) -> PurchaseRequires an open connection
React Native / Expo: wait for the hook's connected flag, or call initConnection() and require a true result. Android does not connect implicitly; a disconnected purchase reports not-prepared through the purchase-error event. iOS can connect on demand, but the same gate keeps behavior consistent across platforms.
Parameters# Pass a single RequestPurchaseProps , discriminated by type:
type (required, 'in-app' | 'subs') — Selects the request shape. Use 'in-app' for one-time products and 'subs' for subscriptions.request.apple.sku (iOS only, string) — iOS. Single SKU for the iOS purchase.request.apple.appAccountToken (optional, string) — iOS. UUID-format account token forwarded to Apple. Non-UUID values land as null on the resulting Purchase.request.apple.quantity (optional, number) — iOS. Quantity for consumable bulk purchases.request.apple.billingPlanType (subscriptions only, optional, 'monthly' | 'up-front') — iOS 26.4+. Selects the StoreKit subscription billing plan, such as monthly billing with a 12-month commitment. See iOS commitment billing plans .request.google.skus (Android only, string[]) — Android. Product SKUs to launch the Play purchase flow for.request.google.subscriptionOffers (required for 'subs', { sku: string; offerToken: string }[]) — Android. Required for subscription requests; pair each SKU with its offerToken from fetchProducts.request.google.obfuscatedAccountId (optional, string) — Android. Optional account identifier passed to Play.request.google.obfuscatedProfileId (optional, string) — Android. Optional profile identifier passed to Play.Returns# Promise<Purchase | void> — dispatched purchase payload. Do not rely on this for the actual outcome — listen via purchaseUpdatedListener / purchaseErrorListener instead.
Throws# Invalid arguments or failures that prevent dispatch can reject the promise. Store outcomes are event-based in React Native; Android not-prepared arrives through purchaseErrorListener or the hook's onPurchaseError callback.
Example guard try await OpenIapModule.shared.initConnection() else { return }
try await OpenIapModule.shared.requestPurchase(
RequestPurchaseProps(
request: .purchase(RequestPurchasePropsByPlatforms(
apple: RequestPurchaseIosProps(sku: "com.app.premium")
)),
type: .inApp
)
)Kotlin example check(openIapStore.initConnection()) { "Store connection failed" }
openIapStore.requestPurchase(
RequestPurchaseProps(
request = RequestPurchaseProps.Request.Purchase(
RequestPurchasePropsByPlatforms(
google = RequestPurchaseAndroidProps(skus = listOf("com.app.premium"))
)
),
type = ProductQueryType.InApp
)
)TypeScript example // expo-iap
import { Button } from 'react-native';
import { initConnection, requestPurchase } from 'expo-iap';
// Same API in react-native-iap:
// import { initConnection, requestPurchase } from 'react-native-iap';
const connected = await initConnection();
if (!connected) throw new Error('Store connection failed');
// One-time product
await requestPurchase({
request: {
apple: { sku: 'com.app.premium' },
google: { skus: ['com.app.premium'] },
},
type: 'in-app',
});
// Subscription
await requestPurchase({
request: {
apple: { sku: 'com.app.monthly' },
google: {
skus: ['com.app.monthly'],
subscriptionOffers: [{ sku: 'com.app.monthly', offerToken: 'offer-token' }],
},
},
type: 'subs',
});
// iOS subscription with monthly billing and a 12-month commitment
await requestPurchase({
request: {
apple: {
sku: 'com.app.annual',
billingPlanType: 'monthly',
},
},
type: 'subs',
});
// --- Or via the useIAP() hook (also exported from react-native-iap) ---
// useIAP wires the purchase listeners for you and exposes the same
// requestPurchase function — handle the result inside onPurchaseSuccess.
import { useIAP } from 'expo-iap';
function BuyButton({ sku }: { sku: string }) {
const { connected, requestPurchase } = useIAP({
onPurchaseSuccess: (purchase) => {
// verify + finishTransaction here
},
onPurchaseError: (error) => {
console.warn('Purchase failed', error);
},
});
return (
<Button
title="Buy"
disabled={!connected}
onPress={() => {
void requestPurchase({
request: { apple: { sku }, google: { skus: [sku] } },
type: 'in-app',
}).catch((error) => {
console.warn('Purchase dispatch failed', error);
});
}}
/>
);
}Dart example final connected = await FlutterInappPurchase.instance.initConnection();
if (!connected) throw StateError('Store connection failed');
await FlutterInappPurchase.instance.requestPurchase(
RequestPurchaseProps.inApp((
apple: RequestPurchaseIosProps(sku: 'com.app.premium'),
google: RequestPurchaseAndroidProps(skus: ['com.app.premium']),
)),
);
// --- Or via the builder DSL ---
// requestPurchaseWithBuilder mirrors the props above but lets you assign
// platform-specific fields fluently inside the build closure.
final iap = FlutterInappPurchase.instance;
await iap.requestPurchaseWithBuilder(
build: (builder) {
builder
..type = ProductQueryType.InApp
..android.skus = ['com.app.premium']
..ios.sku = 'com.app.premium';
},
);
// Single-platform builder DSL (Android only)
await iap.requestPurchaseWithBuilder(
build: (builder) {
builder
..type = ProductQueryType.InApp
..android.skus = ['com.app.premium'];
},
);C# (MAUI) example using OpenIap;
using OpenIap.Maui;
async Task HandlePurchaseSafelyAsync(Purchase purchase)
{
try
{
if (!await VerifyPurchaseOnServerAsync(purchase))
throw new InvalidOperationException("Purchase verification failed");
await GrantEntitlementAsync(purchase.ProductId);
// Finish last (Android auto-refunds after 3 days otherwise!).
await ((MutationResolver)OpenIapClient.Instance).FinishTransactionAsync(
purchase: new PurchaseInput(purchase),
isConsumable: true);
}
catch (Exception error)
{
Console.WriteLine(
quot;Purchase processing failed: {error.Message}");
}
}
// Subscribe to results FIRST — requestPurchase is event-based.
OpenIapClient.Instance.PurchaseUpdated.Subscribe(
purchase => _ = HandlePurchaseSafelyAsync(purchase));
OpenIapClient.Instance.PurchaseError.Subscribe(error => {
Console.WriteLine(
quot;{error.Code}: {error.Message}");
});
if (!await ((MutationResolver)OpenIapClient.Instance).InitConnectionAsync())
return;
// Then request the purchase
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" } },
},
Type = ProductQueryType.InApp,
});Kotlin (KMP) example check(kmpIAP.initConnection()) { "Store connection failed" }
kmpIAP.requestPurchase(
RequestPurchaseProps(
request = RequestPurchaseProps.Request.Purchase(
RequestPurchasePropsByPlatforms(
google = RequestPurchaseAndroidProps(skus = listOf("com.app.premium"))
)
),
type = ProductQueryType.InApp
)
)
// --- Or via the DSL API ---
// Platform-specific options are configured inside apple { } / google { }
// blocks; you can include either or both depending on which stores you ship.
val purchase = kmpIAP.requestPurchase {
apple {
sku = "com.app.premium"
quantity = 1
}
google {
skus = listOf("com.app.premium")
}
}
// Single-platform DSL (Android only)
kmpIAP.requestPurchase {
google {
skus = listOf("com.app.premium")
}
}GDScript example if not await iap.init_connection():
push_error("Store connection failed")
return
var props = RequestPurchaseProps.new()
props.request = RequestPurchasePropsByPlatforms.new()
props.request.apple = RequestPurchaseIosProps.new()
props.request.apple.sku = "com.app.premium"
props.type = ProductQueryType.IN_APP
await iap.request_purchase(props)See: RequestPurchaseProps