Skip to main content
Version: 6.7

Purchase States

Types and enums representing the state of purchases and transactions.

Purchase Class​

Main class representing a completed or pending purchase.

class Purchase {
final String productId;
final String? transactionId;
final String? transactionReceipt;
final String? purchaseToken;
final DateTime? transactionDate;
final IAPPlatform platform;
final bool? isAcknowledgedAndroid;
final String? purchaseStateAndroid;
final String? originalTransactionIdentifierIOS;
final Map<String, dynamic>? originalJson;

// StoreKit 2 specific fields
final String? transactionState;
final bool? isUpgraded;
final DateTime? expirationDate;
final DateTime? revocationDate;
final int? revocationReason;
}

Common Properties​

  • productId - The product identifier that was purchased
  • transactionId - Unique transaction identifier
  • transactionReceipt - Receipt data (iOS primarily)
  • purchaseToken - Purchase token (Android primarily)
  • transactionDate - When the transaction occurred
  • platform - Platform where purchase was made

Android-Specific Properties​

  • isAcknowledgedAndroid - Whether purchase has been acknowledged
  • purchaseStateAndroid - Current state of the purchase

iOS-Specific Properties​

  • originalTransactionIdentifierIOS - Original transaction ID for renewals
  • transactionState - Current transaction state
  • isUpgraded - Whether subscription was upgraded
  • expirationDate - When subscription expires
  • revocationDate - When purchase was revoked
  • revocationReason - Reason for revocation

PurchaseState (Android)​

Enum representing the state of an Android purchase.

enum PurchaseState {
pending, // Purchase is pending (awaiting payment)
purchased, // Purchase completed successfully
unspecified // Unknown/unspecified state
}

Usage​

void handleAndroidPurchase(Purchase item) {
switch (item.purchaseStateAndroid) {
case PurchaseState.purchased:
// Purchase completed - safe to deliver content
deliverContent(item.productId);
break;
case PurchaseState.pending:
// Payment pending - wait for completion
showPendingMessage();
break;
case PurchaseState.unspecified:
// Unknown state - handle cautiously
logUnknownState(item);
break;
}
}

TransactionState (iOS)​

Enum representing the state of an iOS transaction.

enum TransactionState {
purchasing, // Transaction is being processed
purchased, // Transaction completed successfully
failed, // Transaction failed
restored, // Transaction was restored
deferred // Transaction pending external approval (e.g., Ask to Buy)
}

Usage​

void handleIOSTransaction(Purchase item) {
switch (item.transactionStateIOS) {
case TransactionState.purchased:
case TransactionState.restored:
// Safe to deliver content and finish transaction
deliverContent(item.productId);
FlutterInappPurchase.instance.finishTransaction(item);
break;
case TransactionState.failed:
// Transaction failed - don't deliver content
showErrorMessage();
// Still need to finish failed transactions
FlutterInappPurchase.instance.finishTransaction(item);
break;
case TransactionState.purchasing:
// Still processing - wait
showLoadingIndicator();
break;
case TransactionState.deferred:
// Waiting for parental approval
showDeferredMessage();
break;
}
}

Complete State Handling​

class PurchaseStateHandler {
void processPurchase(Purchase purchase) {
if (Platform.isAndroid) {
_handleAndroidPurchase(purchase);
} else if (Platform.isIOS) {
_handleIOSPurchase(purchase);
}
}

void _handleAndroidPurchase(Purchase purchase) {
switch (purchase.purchaseStateAndroid) {
case PurchaseState.purchased:
if (purchase.isAcknowledgedAndroid == false) {
// Need to acknowledge within 3 days
_acknowledgePurchase(purchase);
}
_deliverContent(purchase);
break;

case PurchaseState.pending:
// Store pending purchase for later processing
_storePendingPurchase(purchase);
_showPendingUI();
break;

case PurchaseState.unspecified:
// Log for investigation
_logUnknownPurchaseState(purchase);
break;
}
}

void _handleIOSPurchase(Purchase purchase) {
switch (purchase.transactionStateIOS) {
case TransactionState.purchased:
_deliverContent(purchase);
_finishTransaction(purchase);
break;

case TransactionState.restored:
_restoreContent(purchase);
_finishTransaction(purchase);
break;

case TransactionState.failed:
_handleFailure(purchase);
_finishTransaction(purchase); // Still need to finish
break;

case TransactionState.deferred:
_handleDeferred(purchase);
// Don't finish - wait for final state
break;

case TransactionState.purchasing:
_showPurchasingUI();
// Don't finish - wait for completion
break;
}
}
}

State Transitions​

Android Purchase Flow​

User initiates purchase
↓
Payment method selected
↓
PurchaseState.pending (if async payment)
↓
Payment processed
↓
PurchaseState.purchased
↓
App acknowledges purchase
↓
Purchase complete

iOS Transaction Flow​

User initiates purchase
↓
TransactionState.purchasing
↓
Payment processed
↓
TransactionState.purchased
↓
App delivers content
↓
App finishes transaction
↓
Transaction removed from queue

Error States and Recovery​

class StateRecoveryHandler {
Future<void> recoverPendingStates() async {
// Get all available purchases
final purchases = await FlutterInappPurchase.instance.getAvailablePurchases();

for (var purchase in purchases) {
await _recoverPurchaseState(purchase);
}
}

Future<void> _recoverPurchaseState(Purchase purchase) async {
if (Platform.isAndroid) {
// Check if acknowledgment is needed
if (purchase.purchaseStateAndroid == PurchaseState.purchased &&
purchase.isAcknowledgedAndroid == false) {

// Check if content was already delivered
if (await _wasContentDelivered(purchase.transactionId)) {
// Just acknowledge without re-delivering
await _acknowledgePurchaseOnly(purchase);
} else {
// Deliver content and acknowledge
await _deliverContentAndAcknowledge(purchase);
}
}
} else if (Platform.isIOS) {
// Check for unfinished transactions
final pending = await FlutterInappPurchase.instance.getPendingTransactionsIOS();

for (var transaction in pending ?? []) {
if (transaction.transactionStateIOS == TransactionState.purchased ||
transaction.transactionStateIOS == TransactionState.restored) {

// Verify content delivery
if (!await _wasContentDelivered(transaction.transactionId)) {
await _deliverContent(transaction);
}

// Finish the transaction
await FlutterInappPurchase.instance.finishTransactionIOS(transaction);
}
}
}
}
}

Best Practices​

State Checking​

bool isPurchaseComplete(Purchase purchase) {
if (Platform.isAndroid) {
return purchase.purchaseStateAndroid == PurchaseState.purchased &&
purchase.isAcknowledgedAndroid == true;
} else if (Platform.isIOS) {
// On iOS, if we receive the purchase, it's valid
// State checking is for transaction management
return true;
}
return false;
}

bool needsAcknowledgment(Purchase purchase) {
return Platform.isAndroid &&
purchase.purchaseStateAndroid == PurchaseState.purchased &&
purchase.isAcknowledgedAndroid == false;
}

bool canFinishTransaction(Purchase item) {
if (Platform.isIOS) {
return item.transactionStateIOS == TransactionState.purchased ||
item.transactionStateIOS == TransactionState.restored ||
item.transactionStateIOS == TransactionState.failed;
}
return true; // Android transactions can always be "finished"
}

State Persistence​

class PurchaseStateManager {
final SharedPreferences prefs;

Future<void> savePurchaseState(Purchase purchase) async {
final stateData = {
'productId': purchase.productId,
'transactionId': purchase.transactionId,
'state': Platform.isAndroid
? purchase.purchaseStateAndroid
: purchase.transactionState,
'acknowledged': purchase.isAcknowledgedAndroid,
'timestamp': DateTime.now().millisecondsSinceEpoch,
};

await prefs.setString(
'purchase_state_${purchase.transactionId}',
json.encode(stateData),
);
}

Future<Map<String, dynamic>?> getPurchaseState(String transactionId) async {
final stateJson = prefs.getString('purchase_state_$transactionId');
return stateJson != null ? json.decode(stateJson) : null;
}
}