OpenIAP is a unified specification for in-app purchases across platforms, frameworks, and emerging technologies. This Apple ecosystem implementation standardizes IAP implementations to reduce fragmentation and enable consistent behavioral across all Apple platforms.
In the AI coding era, having a unified IAP specification becomes increasingly important as developers build applications across multiple platforms and frameworks with automated tools.
Visit openiap.dev for complete documentation, guides, and the full OpenIAP specification.
- β StoreKit 2 support with full iOS 15+ compatibility
- β Cross-platform support (iOS, macOS, tvOS, watchOS)
- β Thread-safe operations with MainActor isolation
- β Explicit connection management with automatic listener cleanup
- β Multiple API levels - From simple global functions to advanced control
- β Product management with intelligent caching
- β
Purchase handling with automatic transaction verification
- Processes only StoreKit 2 verified transactions and emits updates.
- β
Subscription management with cancel/reactivate support
- Opens App Store manage subscriptions UI for user cancel/reactivate and detects state changes.
- β
Receipt validation and transaction security
- Provides Base64 receipt and JWS; verifies latest transaction via StoreKit and supports server-side validation.
- β Event-driven purchase observation
- β Swift Package Manager and CocoaPods support
Platform | Minimum Version |
---|---|
iOS | 15.0+ |
macOS | 14.0+ |
tvOS | 15.0+ |
watchOS | 8.0+ |
Swift | 5.9+ |
Add OpenIAP to your Package.swift
:
dependencies: [
.package(url: "https://github.com/hyodotdev/openiap-apple.git", from: "1.1.9")
]
Or through Xcode:
- File β Add Package Dependencies
- Enter:
https://github.com/hyodotdev/openiap-apple.git
- Select version and add to your target
Add to your Podfile
:
pod 'openiap', '~> 1.1.9'
Then run:
pod install
OpenIAP provides multiple ways to integrate in-app purchases, from super simple one-liners to advanced control. Choose the approach that fits your needs!
Use OpenIapModule's static methods for quick integration:
import OpenIAP
// Initialize connection
let connected = try await OpenIapModule.initConnection()
// Fetch products
let products = try await OpenIapModule.fetchProducts(skus: ["premium", "coins"])
// Make a purchase
let purchase = try await OpenIapModule.requestPurchase(sku: "premium")
// Restore purchases
try await OpenIapModule.restorePurchases()
For more control while keeping it simple:
import OpenIAP
@MainActor
class StoreViewModel: ObservableObject {
private let iapProvider: OpenIapProvider
init() {
// Setup provider with event handlers
self.iapProvider = OpenIapProvider(
onPurchaseSuccess: { purchase in
print("Purchase successful: \(purchase.productId)")
},
onPurchaseError: { error in
print("Purchase failed: \(error.message)")
}
)
Task {
// Initialize connection
try await iapProvider.initConnection()
// Fetch products
try await iapProvider.fetchProducts(
skus: ["product1", "product2"],
type: .inapp
)
}
}
deinit {
Task {
// End connection when done
try await iapProvider.endConnection()
}
}
}
For complete control over the purchase flow:
import OpenIAP
@MainActor
func setupStore() async throws {
let module = OpenIapModule.shared
// Initialize connection first
_ = try await module.initConnection()
// Setup listeners
let subscription = module.purchaseUpdatedListener { purchase in
print("Purchase updated: \(purchase.productId)")
}
// Fetch and purchase
let request = ProductRequest(skus: ["premium"], type: .all)
let products = try await module.fetchProducts(request)
let props = RequestPurchaseProps(sku: "premium")
let purchase = try await module.requestPurchase(props)
// When done, clean up
module.removeListener(subscription)
_ = try await module.endConnection()
}
OpenIAP now has a simplified, minimal API with just 2 main components:
-
OpenIapModule (
OpenIapModule.swift
)- Core StoreKit 2 implementation
- Static convenience methods for simple usage
- Low-level instance methods for advanced control
-
OpenIapProvider (
OpenIapProvider.swift
)- SwiftUI-ready with
@Published
properties - Automatic connection management
- Event callbacks for purchase success/error
- Perfect for MVVM architecture
- SwiftUI-ready with
- No Duplication: Each component has a distinct purpose
- Flexibility: Use global functions, static methods, or instances
- Simplicity: Only 2 files to understand instead of 4+
- Compatibility: Maintains openiap.dev spec compliance
The repository includes a complete SwiftUI example app demonstrating all OpenIAP features:
- Product catalog with real-time pricing
- Purchase flow with loading states and error handling
- Subscription management with renewal tracking, cancel/reactivate support
- Purchase history and transaction details
- Event logging for debugging and monitoring
- Sandbox debug tools integrated into My Purchases section
Run the example:
cd Example
open Martie.xcodeproj
# Via Swift Package Manager
swift test
# Via Xcode
βU (Product β Test)
- Configure your products in App Store Connect
- Create a Sandbox Apple ID
- Use test card:
4242 4242 4242 4242
- Monitor purchase events in the Example app logs
OpenIAP provides comprehensive transaction verification with server-side receipt validation:
// Request purchase without auto-finishing
let purchase = try await requestPurchase(
sku: "dev.hyo.premium",
andDangerouslyFinishTransactionAutomatically: false
)
// Validate on your server using transactionReceipt
// Then finish the transaction manually
try await finishTransaction(purchase: purchase, isConsumable: false)
The library provides explicit connection management with automatic listener cleanup.
- Explicit Connection Control: You decide when to connect and disconnect
- Automatic Listener Cleanup: Listeners are cleaned up on endConnection()
- Built-in Event Handling: Purchase success/error callbacks are managed for you
- SwiftUI Ready: Published properties for reactive UI updates
- Simplified API: All common operations with sensible defaults
class StoreViewModel: ObservableObject {
private let iapProvider = OpenIapProvider()
init() {
Task {
// Initialize connection
try await iapProvider.initConnection()
// Fetch products
try await iapProvider.fetchProducts(skus: productIds)
}
}
deinit {
Task {
// End connection (listeners cleaned up automatically)
try await iapProvider.endConnection()
}
}
}
struct OpenIapProduct {
// Common properties
let id: String
let title: String
let description: String
let type: String // "inapp" or "subs"
let displayPrice: String
let currency: String
let price: Double?
let platform: String
// iOS-specific properties
let displayNameIOS: String
let typeIOS: ProductTypeIOS
let subscriptionInfoIOS: SubscriptionInfo?
let discountsIOS: [Discount]?
let isFamilyShareableIOS: Bool
}
enum ProductTypeIOS {
case consumable
case nonConsumable
case autoRenewableSubscription
case nonRenewingSubscription
var isSubs: Bool { /* returns true for autoRenewableSubscription */ }
}
struct OpenIapPurchase {
// Common properties
let id: String // Transaction ID
let productId: String
let transactionDate: Double // Unix timestamp in milliseconds
let transactionReceipt: String
let purchaseState: PurchaseState
let isAutoRenewing: Bool
let quantity: Int
let platform: String
// iOS-specific properties
let appAccountToken: String?
let environmentIOS: String?
let storefrontCountryCodeIOS: String?
let productTypeIOS: String?
let subscriptionGroupIdIOS: String?
let transactionReasonIOS: String? // "PURCHASE" | "RENEWAL"
let offerIOS: PurchaseOffer?
// ... additional properties
}
enum PurchaseState {
case pending, purchased, failed, restored, deferred, unknown
}
struct DiscountOffer {
let identifier: String
let keyIdentifier: String
let nonce: String
let signature: String
let timestamp: String
}
OpenIAP provides comprehensive error handling:
// Unified error model
struct OpenIapError: LocalizedError {
let code: String
let message: String
let productId: String?
var errorDescription: String? { message }
}
// Create errors with predefined codes
let error = OpenIapError(code: "E_USER_CANCELLED", message: "User cancelled the purchase")
We welcome contributions! Please see our Contributing Guidelines for details.
This project is licensed under the MIT License - see the LICENSE file for details.
- Choose the right API level: Start with global functions for simple apps, use UseIAP for SwiftUI
- Handle errors appropriately: Always check for user cancellations vs actual errors
- Validate receipts server-side: Use
andDangerouslyFinishTransactionAutomatically: false
for server validation - Test with Sandbox: Always test purchases in App Store Connect Sandbox environment
- Monitor events: Set up purchase listeners before making purchases
- π Documentation: openiap.dev
- π Bug Reports: GitHub Issues
- π‘ Feature Requests: GitHub Discussions
- π¬ Community: Discord (Coming Soon)