A Tauri plugin for In-App Purchases (IAP) with support for subscriptions on iOS (StoreKit 2), Android (Google Play Billing), Windows (Microsoft Store) and macOS (StoreKit 2).
Install the JavaScript package:
npm install @choochmeque/tauri-plugin-iap-api
# or
yarn add @choochmeque/tauri-plugin-iap-api
# or
pnpm add @choochmeque/tauri-plugin-iap-api
Add the plugin to your Tauri project's Cargo.toml:
[dependencies]
tauri-plugin-iap = "0.9"
Configure the plugin permissions in your capabilities/default.json:
{
"permissions": [
"iap:default"
]
}
Register the plugin in your Tauri app:
fn main() {
tauri::Builder::default()
.plugin(tauri_plugin_iap::init())
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
An example application is available in the examples/iap-demo directory. The example demonstrates all core IAP functionality with a UI:
cd examples/iap-demo
# Install dependencies
pnpm install
# Run in development mode
pnpm tauri dev
# Build for production
pnpm tauri build
Important: The app must be properly code-signed to test IAP functionality: - iOS/macOS: Code signing is required by StoreKit. Configure signing in Xcode or via Tauri's configuration. - Android: Sign your APK/AAB with a release keystore for production testing. - Windows: App must be associated with Microsoft Store and signed for Store submission.
The example app provides a fully functional demo with inline documentation for each IAP feature, making it easy to understand how to integrate the plugin into your own application.
import {
getProducts,
purchase,
restorePurchases,
acknowledgePurchase,
consumePurchase,
getProductStatus,
onPurchaseUpdated,
PurchaseState
} from '@choochmeque/tauri-plugin-iap-api';
// Get available products
const products = await getProducts(['subscription_id_1', 'subscription_id_2'], 'subs');
// Check if user owns a specific product
const status = await getProductStatus('subscription_id_1', 'subs');
if (status.isOwned && status.purchaseState === PurchaseState.PURCHASED) {
console.log('User has active subscription');
if (status.isAutoRenewing) {
console.log('Subscription will auto-renew');
}
}
// Purchase a subscription or in-app product
// Simple purchase (will use first available offer on Android if not specified)
const purchaseResult = await purchase('subscription_id_1', 'subs');
// With specific offer token (Android)
const purchaseResult = await purchase('subscription_id_1', 'subs', {
offerToken: product.subscriptionOfferDetails[0].offerToken
});
// With fraud prevention (Android)
const purchaseResult = await purchase('subscription_id_1', 'subs', {
obfuscatedAccountId: 'hashed_account_id',
obfuscatedProfileId: 'hashed_profile_id'
});
// With app account token (iOS - must be valid UUID)
const purchaseResult = await purchase('subscription_id_1', 'subs', {
appAccountToken: '550e8400-e29b-41d4-a716-446655440000'
});
// Upgrade/downgrade subscription (Android)
import { SubscriptionReplacementMode } from '@choochmeque/tauri-plugin-iap-api';
const upgraded = await purchase('premium_subscription', 'subs', {
offerToken: 'premium_offer_token',
oldPurchaseToken: currentSubscription.purchaseToken,
subscriptionReplacementMode: SubscriptionReplacementMode.WITH_TIME_PRORATION
});
// Restore purchases (specify product type)
const restored = await restorePurchases('subs');
// Acknowledge a non-consumable purchase (subscriptions, durables).
// No-op on iOS/macOS — StoreKit auto-finishes transactions.
await acknowledgePurchase(purchaseResult.purchaseToken);
// Consume a consumable purchase (credits, coins) so it can be re-bought.
// No-op on iOS/macOS — StoreKit auto-allows re-purchase.
await consumePurchase(purchaseResult.purchaseToken);
// Listen for purchase updates
const listener = await onPurchaseUpdated((purchase) => {
console.log('Purchase updated:', purchase);
});
// Stop listening
await listener.unregister();
use tauri_plugin_iap::{IapExt, PurchaseRequest, PurchaseStateValue, Result};
// Get available products
let products = app.iap()
.get_products(
vec!["subscription_id_1".into(), "subscription_id_2".into()],
"subs".into(),
)
.await?;
// Check if user owns a specific product
let status = app.iap()
.get_product_status("subscription_id_1".into(), "subs".into())
.await?;
if status.is_owned && status.purchase_state == Some(PurchaseStateValue::Purchased) {
println!("User has active subscription");
if status.is_auto_renewing == Some(true) {
println!("Subscription will auto-renew");
}
}
// Purchase a subscription or in-app product
// Simple purchase (will use first available offer on Android if not specified)
let purchase_result = app.iap()
.purchase(PurchaseRequest {
product_id: "subscription_id_1".into(),
product_type: "subs".into(),
options: None,
})
.await?;
// Restore purchases (specify product type)
let restored = app.iap().restore_purchases("subs".into()).await?;
// Acknowledge a non-consumable purchase (subscriptions, durables).
// No-op on iOS/macOS — StoreKit auto-finishes transactions.
app.iap().acknowledge_purchase(purchase_result.purchase_token).await?;
tauri.conf.json:
json
{
"bundle": {
"macOS": {
"signingIdentity": "Developer ID Application: Your Name (TEAM_ID)",
"entitlements": "path/to/entitlements.plist"
}
}
}entitlements.plist):
```xml
com.apple.security.app-sandbox ``` 5. Test with sandbox accounts or StoreKit Configuration files 6. Important: App must be code-signed to use StoreKit APIs
initialize() (Deprecated)Deprecated: This function is no longer needed and will be removed in a future major release. The billing client is now initialized automatically when the plugin loads.
Returns { success: true } for backward compatibility.
getProducts(productIds: string[], productType: 'subs' | 'inapp')Fetches product details from the store.
Returns:
- products: Array of product objects with:
- productId: Product identifier
- title: Display name
- description: Product description
- productType: Type of product
- formattedPrice: Localized price string
- subscriptionOfferDetails: (subscriptions only) Array of offers
purchase(productId: string, productType: 'subs' | 'inapp' = 'subs', options?: PurchaseOptions)Initiates a purchase flow with enhanced options for fraud prevention and account management.
Parameters:
- productId: The product to purchase
- productType: Type of product ('subs' for subscriptions, 'inapp' for one-time purchases), defaults to 'subs'
- options: Optional purchase parameters:
- offerToken: (Android) Specific offer to purchase. If not provided, uses first available offer
- obfuscatedAccountId: (Android) Hashed account ID for fraud prevention
- obfuscatedProfileId: (Android) Hashed profile ID for fraud prevention
- appAccountToken: (iOS) UUID string for account tracking and fraud prevention
- oldPurchaseToken: (Android) Purchase token of existing subscription to replace for upgrades/downgrades
- subscriptionReplacementMode: (Android) Proration mode using SubscriptionReplacementMode enum (defaults to WITH_TIME_PRORATION)
Returns: Purchase object with transaction details
restorePurchases(productType: 'subs' | 'inapp' = 'subs')Queries and returns all active purchases.
Parameters:
- productType: Type of products to restore ('subs' or 'inapp'), defaults to 'subs'
getPurchaseHistory()Returns the complete purchase history.
acknowledgePurchase(purchaseToken: string)Acknowledges a non-consumable purchase (subscriptions, durables). On Android this is required within 3 days or Google auto-refunds the purchase. No-op on iOS, macOS, and Windows. Use consumePurchase instead for consumables.
consumePurchase(purchaseToken: string)Consumes a consumable purchase (credits, coins, gems) so it can be purchased again. On Android calls BillingClient.consumeAsync(); on Windows calls StoreContext.ReportConsumableFulfillmentAsync with quantity 1. No-op on iOS and macOS — StoreKit auto-allows re-purchase. Never call both acknowledgePurchase and consumePurchase for the same purchase token.
getProductStatus(productId: string, productType: 'subs' | 'inapp' = 'subs')Checks the ownership and subscription status of a specific product.
Parameters:
- productId: The product identifier to check
- productType: Type of product ('subs' or 'inapp'), defaults to 'subs'
Returns: ProductStatus object with:
- productId: Product identifier
- isOwned: Whether the user currently owns the product
- purchaseState: Current state (PURCHASED=0, CANCELED=1, PENDING=2)
- purchaseTime: When the product was purchased (timestamp)
- expirationTime: (subscriptions only) When the subscription expires
- isAutoRenewing: (subscriptions only) Whether auto-renewal is enabled
- isAcknowledged: Whether the purchase has been acknowledged
- purchaseToken: Token for the purchase transaction
onPurchaseUpdated(callback: (purchase: Purchase) => void): Promise<PluginListener>Listens for purchase state changes.
Returns: A PluginListener object with an unregister() method to stop listening.
$ claude mcp add tauri-plugin-iap \
-- python -m otcore.mcp_server <graph>