Skip to content

hyodotdev/openiap-apple

Repository files navigation

OpenIAP Apple

OpenIAP Logo
A comprehensive Swift implementation of the OpenIAP specification for iOS, macOS, tvOS, and watchOS applications.

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.

🌐 Learn More

Visit openiap.dev for complete documentation, guides, and the full OpenIAP specification.

✨ Features

  • βœ… 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

πŸ“‹ Requirements

Platform Minimum Version
iOS 15.0+
macOS 14.0+
tvOS 15.0+
watchOS 8.0+
Swift 5.9+

πŸ“¦ Installation

Swift Package Manager

Add OpenIAP to your Package.swift:

dependencies: [
    .package(url: "https://github.com/hyodotdev/openiap-apple.git", from: "1.1.9")
]

Or through Xcode:

  1. File β†’ Add Package Dependencies
  2. Enter: https://github.com/hyodotdev/openiap-apple.git
  3. Select version and add to your target

CocoaPods

Add to your Podfile:

pod 'openiap', '~> 1.1.9'

Then run:

pod install

πŸš€ Quick Start

OpenIAP provides multiple ways to integrate in-app purchases, from super simple one-liners to advanced control. Choose the approach that fits your needs!

Option 1: Static Methods (Simplest)

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()

Option 2: OpenIapProvider (SwiftUI Ready)

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()
        }
    }
}

Option 3: OpenIapModule Direct (Low-level)

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()
}

🎯 API Architecture

OpenIAP now has a simplified, minimal API with just 2 main components:

Core Components

  1. OpenIapModule (OpenIapModule.swift)

    • Core StoreKit 2 implementation
    • Static convenience methods for simple usage
    • Low-level instance methods for advanced control
  2. OpenIapProvider (OpenIapProvider.swift)

    • SwiftUI-ready with @Published properties
    • Automatic connection management
    • Event callbacks for purchase success/error
    • Perfect for MVVM architecture

Why This Design?

  • 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

πŸ“± Example App

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

πŸ§ͺ Testing

Run Tests

# Via Swift Package Manager
swift test

# Via Xcode
⌘U (Product β†’ Test)

Test with Sandbox

  1. Configure your products in App Store Connect
  2. Create a Sandbox Apple ID
  3. Use test card: 4242 4242 4242 4242
  4. Monitor purchase events in the Example app logs

Server-Side Validation

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)

πŸ”„ Connection Management

The library provides explicit connection management with automatic listener cleanup.

Key Benefits

  1. Explicit Connection Control: You decide when to connect and disconnect
  2. Automatic Listener Cleanup: Listeners are cleaned up on endConnection()
  3. Built-in Event Handling: Purchase success/error callbacks are managed for you
  4. SwiftUI Ready: Published properties for reactive UI updates
  5. Simplified API: All common operations with sensible defaults

Usage Pattern

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()
        }
    }
}

πŸ“š Data Models

OpenIapProduct

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 */ }
}

OpenIapPurchase

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
}

DiscountOffer

struct DiscountOffer {
    let identifier: String
    let keyIdentifier: String
    let nonce: String
    let signature: String
    let timestamp: String
}

⚑ Error Handling

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")

🀝 Contributing

We welcome contributions! Please see our Contributing Guidelines for details.

πŸ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.

πŸ”„ Best Practices

  1. Choose the right API level: Start with global functions for simple apps, use UseIAP for SwiftUI
  2. Handle errors appropriately: Always check for user cancellations vs actual errors
  3. Validate receipts server-side: Use andDangerouslyFinishTransactionAutomatically: false for server validation
  4. Test with Sandbox: Always test purchases in App Store Connect Sandbox environment
  5. Monitor events: Set up purchase listeners before making purchases

πŸ’¬ Support


Built with ❀️ for the OpenIAP community

About

In-App Purchase in Apple platform that confirms OpenIAP

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Packages

No packages published

Languages