Skip to main content

Release 7.0.0 - OpenIAP GQL 1.0.10, Type Safety & Alternative Billing

· 6 min read
Hyo
Maintainer of flutter_inapp_purchase & expo-iap

Release 7.0.0 migrates to OpenIAP GQL 1.0.10, introducing union types, platform-specific classes, enhanced type safety, and comprehensive alternative billing support for both iOS and Android platforms.

View the release on GitHub →

Highlights

Migration to OpenIAP GQL 1.0.10

The biggest change in 7.0 is the migration to OpenIAP GQL 1.0.10, bringing a new type system that better represents platform-specific IAP features:

// Union types for Products
final result = await iap.fetchProducts(
skus: ['product_id'],
type: ProductQueryType.InApp,
);

if (result is FetchProductsResultProducts) {
for (final product in result.value ?? []) {
if (product is ProductIOS) {
debugPrint('iOS Product: ${product.displayName}');
} else if (product is ProductAndroid) {
debugPrint('Android Product: ${product.title}');
}
}
}

This provides:

  • Better type safety: Platform-specific properties are type-safe
  • Clearer intent: Code explicitly handles platform differences
  • IDE support: Better autocomplete and type hints
  • Runtime safety: Union types prevent accessing wrong platform properties

Platform-Specific Product Types

Products now use a union type system:

// In-app products
ProductIOS | ProductAndroid

// Subscription products
ProductSubscriptionIOS | ProductSubscriptionAndroid

// Purchases
PurchaseIOS | PurchaseAndroid

Each platform has its own specific properties:

if (product is ProductSubscriptionIOS) {
// iOS-specific properties
final offers = product.subscriptionOffers;
final groupId = product.subscriptionGroupIdIOS;
} else if (product is ProductSubscriptionAndroid) {
// Android-specific properties
final offers = product.subscriptionOffers;
final basePlanId = product.basePlanIdAndroid;
}

Non-Nullable Event Streams

Event streams are now non-nullable for better null safety:

// Before (v6.x)
Stream<Purchase?> get purchaseUpdated
Stream<PurchaseResult?> get purchaseError

// After (v7.0)
Stream<Purchase> get purchaseUpdatedListener
Stream<PurchaseError> get purchaseErrorListener

Benefits:

  • No null checks: Guaranteed to receive valid events
  • Clearer naming: Listener suffix indicates event stream
  • Type safety: Removes unnecessary optional handling

Alternative Billing Support

Added comprehensive support for alternative billing on both platforms:

iOS Alternative Billing (StoreKit External Purchase)

Three new APIs for managing external purchases on iOS:

  • canPresentExternalPurchaseNoticeIOS() - Check if the notice sheet is available (iOS 18.2+)
  • presentExternalPurchaseNoticeSheetIOS() - Present a notice before redirecting to external purchase (iOS 18.2+)
  • presentExternalPurchaseLinkIOS(url) - Open external purchase link in Safari (iOS 16.0+)

Configure your iOS app by adding entitlements and Info.plist keys:

<!-- ios/Runner/Runner.entitlements -->
<key>com.apple.developer.storekit.external-purchase</key>
<true/>
<key>com.apple.developer.storekit.external-purchase-link</key>
<true/>
<!-- ios/Runner/Info.plist -->
<key>SKExternalPurchase</key>
<array>
<string>kr</string>
<string>nl</string>
<string>de</string>
</array>

<key>SKExternalPurchaseLink</key>
<dict>
<key>kr</key>
<string>https://your-site.com/kr/checkout</string>
</dict>

Android Alternative Billing

Three new APIs for Google Play Alternative Billing flow:

  • checkAlternativeBillingAvailabilityAndroid() - Check if alternative billing is available
  • showAlternativeBillingDialogAndroid() - Show Google's required information dialog
  • createAlternativeBillingTokenAndroid() - Generate reporting token after payment

Initialize with alternative billing mode:

await iap.initConnection(
alternativeBillingModeAndroid: AlternativeBillingModeAndroid.UserChoice,
);

Available modes:

  • None - Standard Google Play billing only (default)
  • UserChoice - User can choose between Google Play or alternative billing
  • AlternativeOnly - Skip Google Play billing entirely

User Choice Billing Event Listener

New event stream for Android User Choice Billing:

FlutterInappPurchase.instance.userChoiceBillingAndroid.listen((details) {
print('User selected alternative billing');
print('Products: ${details.products}');
print('Token: ${details.externalTransactionToken}');
// Process payment with your system and report token to Google
});

Android Replacement Modes

Subscription upgrade/downgrade now uses replacement modes instead of proration modes:

await iap.requestPurchase(
RequestPurchaseProps.subs((
ios: RequestSubscriptionIosProps(sku: 'yearly_sub'),
android: RequestSubscriptionAndroidProps(
skus: ['yearly_sub'],
replacementModeAndroid: AndroidReplacementMode.withTimeProration,
),
useAlternativeBilling: null,
)),
);

Available modes:

  • withTimeProration (1) - Credit unused time towards new subscription
  • chargeProratedPrice (2) - Charge prorated price immediately
  • withoutProration (3) - No credit for unused time
  • deferred (4) - New subscription starts at next renewal
  • chargeFullPrice (5) - Charge full price immediately

Breaking Changes

Removed Methods

The following deprecated iOS-specific methods have been removed:

  • getAvailableItemsIOS() → Use getAvailablePurchases() instead
  • getAppTransactionTypedIOS() → Use getAppTransactionIOS() instead
  • getPurchaseHistoriesIOS() → Use getAvailablePurchases(onlyIncludeActiveItemsIOS: false) instead

API Changes

  • Event streams renamed: purchaseUpdatedpurchaseUpdatedListener, purchaseErrorpurchaseErrorListener
  • Union types required: Handle FetchProductsResultProducts | FetchProductsResultError explicitly
  • Platform-specific types: Products and Purchases are now platform-specific classes
  • Non-nullable streams: Event streams no longer emit nullable values

Migration Guide

1. Update Event Listeners

// Before
iap.purchaseUpdated.listen((purchase) {
if (purchase != null) {
handlePurchase(purchase);
}
});

// After
iap.purchaseUpdatedListener.listen((purchase) {
handlePurchase(purchase); // No null check needed
});

2. Handle Union Types

// Before
final products = await iap.fetchProducts(...);
for (final product in products) {
print(product.title);
}

// After
final result = await iap.fetchProducts(...);
if (result is FetchProductsResultProducts) {
for (final product in result.value ?? []) {
if (product is ProductIOS) {
print(product.displayName);
} else if (product is ProductAndroid) {
print(product.title);
}
}
}

3. Update Replacement Modes

// Before
RequestPurchaseAndroidProps(
prorationModeAndroid: 1,
)

// After
RequestPurchaseAndroidProps(
replacementModeAndroid: AndroidReplacementMode.withTimeProration,
)

4. Replace Removed Methods

// Before
final items = await iap.getAvailableItemsIOS();

// After
final items = await iap.getAvailablePurchases();

Benefits

Type Safety

The new union type system provides compile-time safety:

  • Platform-specific properties are type-safe
  • No runtime errors from accessing wrong properties
  • Better IDE autocomplete and warnings

OpenIAP Compliance

Aligns with OpenIAP specification across multiple platforms:

  • Expo IAP
  • Flutter In-App Purchase
  • Kotlin Multiplatform SDK
  • React Native IAP (planned)

Better Developer Experience

  • Clearer code: Platform differences are explicit
  • Fewer bugs: Type system catches errors at compile time
  • Better tooling: IDEs provide accurate suggestions
  • Self-documenting: Code intent is clear from types

Alternative Billing Flexibility

  • iOS: External purchase links, notice sheets, multi-link support
  • Android: Alternative billing only, user choice billing, token reporting
  • Unified API: Cross-platform support with platform-specific features

Platform Requirements

iOS Alternative Billing

  • iOS 16.0+ for external purchase URLs
  • iOS 18.2+ for dynamic URLs and notice sheets
  • Approval Required: Must obtain approval from Apple to use external purchase features
  • URL Requirements: URLs must use HTTPS, have no query parameters, and be 1,000 characters or fewer

Android Alternative Billing

  • Google Play Billing Library 5.0+ with alternative billing enabled
  • Approval Required: Must be approved for alternative billing in Google Play Console
  • Token Reporting: Must report tokens to Google within 24 hours

Resources

Acknowledgments

Thanks to all contributors and the OpenIAP community for feedback and testing. Special thanks to early adopters who helped identify issues during the RC phase and provided valuable feedback on the alternative billing implementation.

Ready to upgrade? Check out our updated documentation, migration guide, and examples!