This repository is no longer maintained. All development has moved to the OpenIAP monorepo.
Swift Package:
packages/applein the OpenIAP monorepoInstallation:
- Swift Package Manager: Use
https://github.com/hyodotdev/openiapwith productOpenIAP- CocoaPods:
pod 'openiap'(still published from monorepo)No code changes required - the package name and API remain the same.
π Visit OpenIAP monorepo β
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 - Use
OpenIapModule.sharedorOpenIapStore - β 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.2.23")
]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.2.23'Then run:
pod installOpenIAP 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.shared for quick integration:
import OpenIAP
let module = OpenIapModule.shared
// Initialize connection
_ = try await module.initConnection()
// Fetch products
let products = try await module.fetchProducts(
ProductRequest(skus: ["premium", "coins"], type: .all)
)
// Make a purchase
let purchase = try await module.requestPurchase(
let purchase = try await module.requestPurchase(RequestPurchaseProps(request: .purchase(RequestPurchasePropsByPlatforms(android: nil, ios: RequestPurchaseIosProps(andDangerouslyFinishTransactionAutomatically: nil, appAccountToken: nil, quantity: 1, sku: "premium", withOffer: nil))), type: .inApp))
)
// Get available/restored purchases
let restored = try await module.getAvailablePurchases(nil)
// End connection when done
_ = try await module.endConnection()For more control while keeping it simple:
import OpenIAP
@MainActor
class StoreViewModel: ObservableObject {
private let iapStore: OpenIapStore
init() {
// Setup store with event handlers
self.iapStore = OpenIapStore(
onPurchaseSuccess: { purchase in
print("Purchase successful: \(purchase.productId)")
},
onPurchaseError: { error in
print("Purchase failed: \(error.message)")
}
)
Task {
// Initialize connection
try await iapStore.initConnection()
// Fetch products
try await iapStore.fetchProducts(
skus: ["product1", "product2"],
type: .inApp
)
}
}
deinit {
Task {
// End connection when done
try await iapStore.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 purchase = try await store.requestPurchase(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
- Shared instance for simple usage
- Low-level instance methods for advanced control
-
OpenIapStore (
OpenIapStore.swift)- SwiftUI-ready with
@Publishedproperties - Explicit connection management (initConnection/endConnection)
- Event callbacks for purchase success/error
- Perfect for MVVM architecture
- SwiftUI-ready with
- No Duplication: Each component has a distinct purpose
- Flexibility: Use the shared module or the SwiftUI store
- Simplicity: Only 2 files to understand instead of 4+
- Compatibility: Maintains openiap.dev spec compliance
# 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
OpenIAP provides comprehensive transaction verification with server-side receipt validation:
let store = OpenIapStore()
try await store.initConnection()
// Request purchase (validate server-side first)
let purchase = try await store.requestPurchase(
try await store.requestPurchase(sku: "dev.hyo.premium")
)
// Validate on your server using purchase.purchaseToken
// Then finish the transaction manually
_ = try await store.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 iapStore = OpenIapStore()
init() {
Task {
// Initialize connection
try await iapStore.initConnection()
// Fetch products
try await iapStore.fetchProducts(skus: productIds)
}
}
deinit {
Task {
// End connection (listeners cleaned up automatically)
try await iapStore.endConnection()
}
}
}Our Swift data models are generated from the shared GraphQL schema in openiap-gql. Run ./scripts/generate-types.sh (or the equivalent tooling in that repo) to update Sources/Models/Types.swift, and every consumerβincluding the example appβshould rely on those generated definitions instead of hand-written structs.
ProductIOS snapshot
struct ProductIOS {
let id: String
let title: String
let description: String
let type: ProductType
let displayPrice: String
let currency: String
let price: Double?
let platform: IapPlatform
// iOS-specific properties
let displayNameIOS: String
let typeIOS: ProductTypeIOS
let subscriptionInfoIOS: SubscriptionInfoIOS?
let discountsIOS: [DiscountIOS]?
let isFamilyShareableIOS: Bool
}PurchaseIOS snapshot
struct PurchaseIOS {
let id: String
let productId: String
let transactionDate: Double
let purchaseToken: String?
let purchaseState: PurchaseState
let isAutoRenewing: Bool
let quantity: Int
let platform: IapPlatform
// iOS-specific properties
let appAccountToken: String?
let environmentIOS: String?
let storefrontCountryCodeIOS: String?
let subscriptionGroupIdIOS: String?
let transactionReasonIOS: String?
let offerIOS: PurchaseOfferIOS?
}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 PurchaseError: LocalizedError {
let code: String
let message: String
let productId: String?
var errorDescription: String? { message }
}
// Create errors with predefined codes
let error = PurchaseError(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: Use
OpenIapModule.sharedfor simple flows, orOpenIapStorefor SwiftUI apps - Handle errors appropriately: Always check for user cancellations vs actual errors
- Validate receipts server-side: Use
andDangerouslyFinishTransactionAutomatically: falsefor 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)
