Release 7.0.0 - OpenIAP GQL 1.0.10, Type Safety & Alternative Billing
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.
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 availableshowAlternativeBillingDialogAndroid()
- Show Google's required information dialogcreateAlternativeBillingTokenAndroid()
- 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 billingAlternativeOnly
- 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 subscriptionchargeProratedPrice
(2) - Charge prorated price immediatelywithoutProration
(3) - No credit for unused timedeferred
(4) - New subscription starts at next renewalchargeFullPrice
(5) - Charge full price immediately
Breaking Changes
Removed Methods
The following deprecated iOS-specific methods have been removed:
getAvailableItemsIOS()
→ UsegetAvailablePurchases()
insteadgetAppTransactionTypedIOS()
→ UsegetAppTransactionIOS()
insteadgetPurchaseHistoriesIOS()
→ UsegetAvailablePurchases(onlyIncludeActiveItemsIOS: false)
instead
API Changes
- Event streams renamed:
purchaseUpdated
→purchaseUpdatedListener
,purchaseError
→purchaseErrorListener
- 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
- API Documentation
- Type Reference
- Migration Guide
- Alternative Billing Guide
- Alternative Billing Example
- Complete Examples
- GitHub Release
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!