Lifecycle
For complete understanding of the in-app purchase lifecycle, flow diagrams, and state management, please visit:
Lifecycle Documentation - openiap.dev

The Open IAP specification provides detailed documentation on:
- Complete purchase flow
- State transitions and management
- Connection lifecycle
- Error recovery patterns
- Platform-specific considerations
Implementation with GodotIap
Connection Management
Understanding the connection lifecycle is crucial for reliable IAP implementation.
Connection States
The connection can be in several states:
- Disconnected: Initial state, no connection to store
- Connecting: Attempting to establish connection
- Connected: Successfully connected, ready for operations
- Error: Connection failed
Basic Connection Flow
extends Node
var iap: GodotIap
var is_connected: bool = false
func _ready():
if not Engine.has_singleton("GodotIap"):
print("GodotIap not available")
return
iap = Engine.get_singleton("GodotIap")
_setup_signals()
_initialize_connection()
func _setup_signals():
iap.purchase_updated.connect(_on_purchase_updated)
iap.purchase_error.connect(_on_purchase_error)
iap.products_fetched.connect(_on_products_fetched)
func _initialize_connection():
var result = JSON.parse_string(iap.init_connection())
if result.get("success", false):
is_connected = true
print("Connected to store")
_load_products()
else:
print("Failed to connect: ", result.get("error", ""))
func _load_products():
if is_connected:
var product_ids = ["coins_100", "premium"]
iap.fetch_products(JSON.stringify(product_ids), "inapp")
Handling Connection States
var connection_error: String = ""
func _initialize_connection():
var result = JSON.parse_string(iap.init_connection())
if result.get("success", false):
is_connected = true
connection_error = ""
_on_connected()
else:
is_connected = false
connection_error = result.get("error", "Unknown error")
_on_connection_failed()
func _on_connected():
print("Store connected, loading products...")
_load_products()
func _on_connection_failed():
print("Connection failed: ", connection_error)
# Show retry UI to user
show_retry_button()
func retry_connection():
print("Retrying connection...")
_initialize_connection()
Component Lifecycle Integration
Scene Lifecycle
extends Node
var iap: GodotIap
func _ready():
# Initialize IAP when scene loads
if Engine.has_singleton("GodotIap"):
iap = Engine.get_singleton("GodotIap")
_setup_signals()
_initialize()
func _exit_tree():
# Clean up when scene is removed
if iap:
# Disconnect signals if needed
if iap.purchase_updated.is_connected(_on_purchase_updated):
iap.purchase_updated.disconnect(_on_purchase_updated)
if iap.purchase_error.is_connected(_on_purchase_error):
iap.purchase_error.disconnect(_on_purchase_error)
# End connection
iap.end_connection()
func _setup_signals():
iap.purchase_updated.connect(_on_purchase_updated)
iap.purchase_error.connect(_on_purchase_error)
iap.products_fetched.connect(_on_products_fetched)
func _initialize():
var result = JSON.parse_string(iap.init_connection())
if result.get("success", false):
_load_products()
func _on_purchase_updated(purchase: Dictionary):
# Handle purchase updates
pass
func _on_purchase_error(error: Dictionary):
# Handle purchase errors
pass
func _on_products_fetched(products: Array):
# Handle fetched products
pass
Autoload Pattern (Recommended)
For persistent IAP management, use an Autoload singleton:
# iap_manager.gd - Add this as an Autoload
extends Node
var iap: GodotIap
var is_connected: bool = false
var products: Array = []
var subscriptions: Array = []
signal iap_connected
signal iap_disconnected
signal iap_error(error: Dictionary)
signal products_loaded(products: Array)
signal purchase_completed(purchase: Dictionary)
signal purchase_failed(error: Dictionary)
func _ready():
if Engine.has_singleton("GodotIap"):
iap = Engine.get_singleton("GodotIap")
_setup_signals()
_initialize()
else:
push_warning("GodotIap singleton not available")
func _setup_signals():
iap.purchase_updated.connect(_on_purchase_updated)
iap.purchase_error.connect(_on_purchase_error)
iap.products_fetched.connect(_on_products_fetched)
iap.subscriptions_fetched.connect(_on_subscriptions_fetched)
func _initialize():
var result = JSON.parse_string(iap.init_connection())
if result.get("success", false):
is_connected = true
iap_connected.emit()
else:
iap_error.emit({"message": result.get("error", "")})
func _on_purchase_updated(purchase: Dictionary):
purchase_completed.emit(purchase)
func _on_purchase_error(error: Dictionary):
purchase_failed.emit(error)
func _on_products_fetched(fetched_products: Array):
products = fetched_products
products_loaded.emit(products)
func _on_subscriptions_fetched(fetched_subs: Array):
subscriptions = fetched_subs
# Public API
func load_products(product_ids: Array):
if is_connected:
iap.fetch_products(JSON.stringify(product_ids), "inapp")
func load_subscriptions(sub_ids: Array):
if is_connected:
iap.fetch_subscriptions(JSON.stringify(sub_ids))
func buy(product_id: String, type: String = "inapp"):
if is_connected:
var params = {"sku": product_id, "type": type}
iap.request_purchase(JSON.stringify(params))
func restore():
if is_connected:
return JSON.parse_string(iap.get_available_purchases())
return []
Usage in other scenes:
# In any scene
extends Control
func _ready():
# Connect to the autoload signals
IapManager.products_loaded.connect(_on_products_loaded)
IapManager.purchase_completed.connect(_on_purchase_completed)
# Wait for connection
if IapManager.is_connected:
_load_store()
else:
IapManager.iap_connected.connect(_on_iap_connected)
func _on_iap_connected():
_load_store()
func _load_store():
IapManager.load_products(["coins_100", "premium"])
func _on_products_loaded(products: Array):
update_store_ui(products)
func _on_purchase_completed(purchase: Dictionary):
handle_purchase(purchase)
Best Practices
Do
- Initialize early: Connect to store as early as possible in app lifecycle
- Handle connection states: Provide feedback to users about connection status
- Use Autoload for persistence: Keep IAP manager as singleton
- Clean up properly: Always remove listeners and end connections
# Good: Using Autoload pattern
func _ready():
if IapManager.is_connected:
load_store()
Don't
- Initialize repeatedly: Don't call init_connection/end_connection for every operation
- Ignore connection state: Don't attempt store operations when disconnected
- Forget cleanup: Always clean up listeners to prevent issues
# Bad: Initializing for every operation
func bad_purchase_flow(product_id: String):
iap.init_connection() # Don't do this
iap.request_purchase(JSON.stringify({"sku": product_id}))
iap.end_connection() # Don't do this
# Good: Use existing connection
func good_purchase_flow(product_id: String):
if is_connected:
iap.request_purchase(JSON.stringify({"sku": product_id}))
Purchase Flow Best Practices
Purchase Verification and Security
-
Server-side verification recommended: For production apps, verify purchases on your secure server before granting content.
-
Finish transactions after verification: Always call
finish_transactionafter successfully verifying a purchase. -
Never trust client-side data: Always verify purchases server-side before granting premium content.
Purchase State Management
-
Handle all purchase states: Including pending, failed, restored, and cancelled purchases.
-
Handle pending purchases: Some purchases may require approval and remain in pending state.
-
Restore purchases properly: Implement purchase restoration for non-consumable products and subscriptions.
Error Handling and User Experience
-
Implement comprehensive error handling: Provide meaningful feedback for different error scenarios.
-
Graceful degradation: Your app should work even if purchases fail.
-
User feedback: Keep users informed about purchase status.
Testing and Development
-
Test thoroughly: Use real devices and official test accounts.
-
Monitor purchase flow: Log important events for debugging.
Common Pitfalls and Solutions
Transaction Management Issues
Not finishing transactions:
# Wrong - forgetting to finish transaction
func handle_purchase(purchase: Dictionary):
var verified = await validate_receipt(purchase)
# Missing: finish_transaction call
Always finish transactions after validation:
# Correct - always finish transaction
func handle_purchase(purchase: Dictionary):
var verified = await validate_receipt(purchase)
if verified:
grant_content(purchase.productId)
var params = {
"purchase": purchase,
"isConsumable": is_consumable(purchase.productId)
}
iap.finish_transaction(JSON.stringify(params))
Security Issues
Trusting client-side validation:
# Wrong - never trust client-side validation alone
func handle_purchase(purchase: Dictionary):
grant_premium_feature() # Not secure
Always validate server-side:
# Correct - validate on secure server
func handle_purchase(purchase: Dictionary):
var is_valid = await your_api.validate_receipt(purchase.purchaseToken)
if is_valid:
grant_premium_feature()
finish_transaction(purchase)
App Lifecycle Issues
Not handling app restarts:
Purchases can complete after app restart, so always check for pending purchases on launch:
# Correct - check for purchases on app launch
func _ready():
# After connection established
if is_connected:
check_pending_purchases()
func check_pending_purchases():
var purchases = JSON.parse_string(iap.get_available_purchases())
for purchase in purchases:
await process_purchase(purchase)
Connection Management Issues
Initializing connection repeatedly:
# Wrong - don't initialize for every operation
func purchase_product(sku: String):
iap.init_connection() # Don't do this
iap.request_purchase(...)
iap.end_connection() # Don't do this
Maintain single connection:
# Correct - use existing connection
func purchase_product(sku: String):
if is_connected:
iap.request_purchase(...)
else:
print("Store not connected")
Next Steps
- Review Purchase Implementation Guide for detailed code examples
- Check out Error Handling Guide for debugging tips
- See API Reference for detailed method documentation
