Skip to main content
Version: 6.3

iOS Setup

For complete iOS setup instructions including App Store Connect configuration, Xcode setup, and testing guidelines, please visit:

👉 iOS Setup Guide - openiap.dev

The guide covers:

  • App Store Connect configuration
  • Xcode project setup
  • Sandbox testing
  • Common troubleshooting steps

Code Implementation​

Basic Setup​

import 'package:flutter_inapp_purchase/flutter_inapp_purchase.dart';

final List<String> iosProductIds = [
'com.yourapp.premium_upgrade',
'com.yourapp.remove_ads',
'com.yourapp.monthly_subscription',
];

class IOSStoreExample extends StatefulWidget {

_IOSStoreExampleState createState() => _IOSStoreExampleState();
}

class _IOSStoreExampleState extends State<IOSStoreExample> {
late StreamSubscription _purchaseUpdatedSubscription;
late StreamSubscription _purchaseErrorSubscription;
List<IapItem> _products = [];
bool _isAvailable = false;


void initState() {
super.initState();
initPlatformState();
}

Future<void> initPlatformState() async {
// Initialize connection
final result = await FlutterInappPurchase.instance.initConnection();
print('Connection result: $result');

if (!mounted) return;

setState(() {
_isAvailable = result;
});

// Listen for purchase updates
_purchaseUpdatedSubscription =
FlutterInappPurchase.purchaseUpdated.listen((purchase) {
print('Purchase updated: ${purchase?.productId}');
_handlePurchaseUpdate(purchase);
});

_purchaseErrorSubscription =
FlutterInappPurchase.purchaseError.listen((error) {
print('Purchase error: ${error?.message}');
_handlePurchaseError(error);
});

// Get products if connected
if (_isAvailable) {
await _getProducts();
}
}

Future<void> _getProducts() async {
try {
final products = await FlutterInappPurchase.instance.getProducts(iosProductIds);
setState(() {
_products = products;
});
} catch (error) {
print('Failed to get products: $error');
}
}

void _handlePurchaseUpdate(PurchaseResult? purchase) {
if (purchase != null) {
switch (purchase.purchaseStateIOS) {
case PurchaseState.purchased:
_verifyAndFinishPurchase(purchase);
break;
case PurchaseState.restored:
print('Purchase restored: ${purchase.productId}');
break;
case PurchaseState.purchasing:
print('Purchase in progress: ${purchase.productId}');
break;
case PurchaseState.deferred:
print('Purchase deferred: ${purchase.productId}');
break;
case PurchaseState.failed:
print('Purchase failed: ${purchase.productId}');
break;
}
}
}

void _handlePurchaseError(PurchaseResult? error) {
if (error != null) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Purchase Error'),
content: Text(error.message ?? 'Unknown error occurred'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text('OK'),
),
],
),
);
}
}

Future<void> _verifyAndFinishPurchase(PurchaseResult purchase) async {
// Verify purchase on your server
final isValid = await _verifyPurchaseOnServer(purchase);

if (isValid) {
// Grant access to content
await _grantPurchaseContent(purchase);

// Finish the transaction
// Finish the transaction (iOS: ignores isConsumable)
await FlutterInappPurchase.instance.finishTransaction(purchase);

ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Purchase successful!')),
);
} else {
print('Purchase verification failed');
}
}

Future<bool> _verifyPurchaseOnServer(PurchaseResult purchase) async {
// Implement server-side receipt validation
// This is a placeholder - implement your actual validation logic
return true;
}

Future<void> _grantPurchaseContent(PurchaseResult purchase) async {
// Grant the purchased content to the user
print('Granting content for: ${purchase.productId}');
}


Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('iOS Store'),
),
body: Column(
children: [
Text('Store Available: $_isAvailable'),
Expanded(
child: ListView.builder(
itemCount: _products.length,
itemBuilder: (context, index) {
final product = _products[index];
return IOSProductTile(
product: product,
onPurchase: () => _purchaseProduct(product),
);
},
),
),
],
),
);
}

Future<void> _purchaseProduct(IapItem product) async {
try {
await FlutterInappPurchase.instance.requestPurchase(
RequestPurchase(
ios: RequestPurchaseIosProps(sku: product.productId!),
),
PurchaseType.inapp,
);
} catch (error) {
print('Purchase request failed: $error');
}
}


void dispose() {

void dispose() {
_purchaseUpdatedSubscription.cancel();
_purchaseErrorSubscription.cancel();
FlutterInappPurchase.instance.endConnection();
super.dispose();
}
}

class IOSProductTile extends StatelessWidget {
final IapItem product;
final VoidCallback onPurchase;

const IOSProductTile({
Key? key,
required this.product,
required this.onPurchase,
}) : super(key: key);


Widget build(BuildContext context) {
return Card(
margin: EdgeInsets.all(8.0),
child: ListTile(
title: Text(product.title ?? 'Unknown Product'),
subtitle: Text(product.description ?? 'No description'),
trailing: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
product.localizedPrice ?? 'N/A',
style: TextStyle(fontWeight: FontWeight.bold),
),
ElevatedButton(
onPressed: onPurchase,
child: Text('Buy'),
),
],
),
),
);
}
}

iOS-Specific Features​

StoreKit 2 Support​

// Check StoreKit 2 availability
Future<void> checkStoreKit2Support() async {
if (Platform.isIOS) {
final version = await FlutterInappPurchase.instance.getIOSVersion();
final isStoreKit2Available = version >= 15.0;
print('StoreKit 2 available: $isStoreKit2Available');
}
}

Subscription Management​

// Show subscription management page
Future<void> showSubscriptionManagement() async {
if (Platform.isIOS) {
await FlutterInappPurchase.instance.showManageSubscriptions();
}
}

// Present code redemption sheet
Future<void> redeemCode() async {
if (Platform.isIOS) {
await FlutterInappPurchase.instance.presentCodeRedemptionSheet();
}
}

Restore Purchases​

Future<void> restorePurchases() async {
try {
final restoredPurchases = await FlutterInappPurchase.instance.restoreTransactions();
print('Restored ${restoredPurchases.length} purchases');

for (var purchase in restoredPurchases) {
await _verifyAndFinishPurchase(purchase);
}
} catch (error) {
print('Restore failed: $error');
}
}

Error Handling​

void handleIOSError(BuildContext context, PurchaseResult? error) {
if (error?.code != null) {
switch (error!.code) {
case 'E_USER_CANCELLED':
// User cancelled - no action needed
break;
case 'E_PAYMENT_INVALID':
showErrorDialog(context, 'Payment information is invalid');
break;
case 'E_PAYMENT_NOT_ALLOWED':
showErrorDialog(context, 'Payments are not allowed on this device');
break;
case 'E_PRODUCT_NOT_AVAILABLE':
showErrorDialog(context, 'This product is not available');
break;
case 'E_RECEIPT_FAILED':
showErrorDialog(context, 'Receipt validation failed');
break;
default:
showErrorDialog(context, 'Purchase failed: ${error.message}');
}
}
}

void showErrorDialog(String message) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Error'),
content: Text(message),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text('OK'),
),
],
),
);
}

Common Issues​

Products Not Loading​

Problem: getProducts() returns empty list or throws error Solutions:

  • Verify product IDs match exactly between code and App Store Connect
  • Ensure products are Active in App Store Connect
  • Check that all Apple Developer agreements are signed
  • Wait 24 hours after creating products in App Store Connect

Testing Issues​

Problem: "Cannot connect to iTunes Store" error Solutions:

  • Test on real device, not simulator
  • Use proper sandbox tester account
  • Sign out of production Apple ID first
  • Ensure In-App Purchase capability is enabled in Xcode

Receipt Validation​

Problem: Receipt validation failing Solutions:

  • Always validate receipts on your server, not client-side
  • Use Apple's receipt validation API
  • Handle both sandbox and production receipt endpoints
  • Implement proper retry logic for network failures

Next Steps​