Skip to content
This repository was archived by the owner on Oct 16, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .markdownlint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"default": true,
"MD013": false,
"line-length": false,
"MD033": false,
"no-inline-html": false
}
4 changes: 2 additions & 2 deletions Example/OpenIapExample/Screens/PurchaseFlowScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -282,15 +282,15 @@ struct PurchaseFlowScreen: View {
}
}

private func handlePurchaseError(_ error: PurchaseError) {
private func handlePurchaseError(_ error: OpenIapErrorEvent) {
print("❌ [PurchaseFlow] Purchase error: \(error.message)")

// Update UI state
purchaseResultMessage = "❌ Purchase failed: \(error.message)"
showPurchaseResult = true

// Show error alert for non-cancellation errors
if error.code != PurchaseError.E_USER_CANCELLED {
if error.code != OpenIapError.E_USER_CANCELLED {
errorMessage = error.message
showError = true
}
Expand Down
3 changes: 1 addition & 2 deletions Example/OpenIapExample/Screens/SubscriptionFlowScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -324,9 +324,8 @@ struct SubscriptionFlowScreen: View {
}
}

private func handlePurchaseError(_ error: PurchaseError) {
private func handlePurchaseError(_ error: OpenIapErrorEvent) {
print("❌ [SubscriptionFlow] Subscription error: \(error.message)")
// Error status is already handled internally by OpenIapStore
}
}

4 changes: 2 additions & 2 deletions Example/OpenIapExample/ViewModels/TransactionObserver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class TransactionObserver: ObservableObject {
errorMessage = nil
}

private func handlePurchaseError(_ error: PurchaseError) {
private func handlePurchaseError(_ error: OpenIapErrorEvent) {
print("❌ Purchase failed - Code: \(error.code), Message: \(error.message)")
errorMessage = error.message
isPending = false
Expand Down Expand Up @@ -96,4 +96,4 @@ struct TransactionObserverExampleView: View {
formatter.timeStyle = .short
return formatter
}
}
}
29 changes: 16 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ import OpenIAP
@MainActor
class StoreViewModel: ObservableObject {
private let iapProvider: OpenIapProvider

init() {
// Setup provider with event handlers
self.iapProvider = OpenIapProvider(
Expand All @@ -120,19 +120,19 @@ class StoreViewModel: ObservableObject {
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
Expand All @@ -152,22 +152,22 @@ 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()
Expand All @@ -179,7 +179,9 @@ func setupStore() async throws {
OpenIAP now has a **simplified, minimal API** with just 2 main components:

### Core Components
1. **OpenIapModule** (`OpenIapModule.swift`)

1. **OpenIapModule** (`OpenIapModule.swift`)

- Core StoreKit 2 implementation
- Static convenience methods for simple usage
- Low-level instance methods for advanced control
Expand All @@ -191,6 +193,7 @@ OpenIAP now has a **simplified, minimal API** with just 2 main components:
- 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+
Expand Down Expand Up @@ -266,17 +269,17 @@ The library provides explicit connection management with automatic listener clea
```swift
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)
Expand Down
35 changes: 33 additions & 2 deletions Sources/Models/OpenIapAppTransaction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,40 @@ extension OpenIapAppTransaction {
}

public struct OpenIapSubscriptionStatus: Codable, Sendable {
public let state: String
public let state: Product.SubscriptionInfo.RenewalState
public let renewalInfo: OpenIapRenewalInfo?

enum CodingKeys: String, CodingKey {
case state
case renewalInfo
}

public init(state: Product.SubscriptionInfo.RenewalState, renewalInfo: OpenIapRenewalInfo?) {
self.state = state
self.renewalInfo = renewalInfo
}

// Encode enum as string for JSON compatibility
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(String(describing: state), forKey: .state)
try container.encodeIfPresent(renewalInfo, forKey: .renewalInfo)
}

// Decode state from string into known cases; fallback to .expired
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let raw = (try? container.decode(String.self, forKey: .state))?.lowercased()
switch raw {
case "subscribed": self.state = .subscribed
case "expired": self.state = .expired
case "inbillingretryperiod": self.state = .inBillingRetryPeriod
case "ingraceperiod": self.state = .inGracePeriod
case "revoked": self.state = .revoked
default: self.state = .expired
}
self.renewalInfo = try container.decodeIfPresent(OpenIapRenewalInfo.self, forKey: .renewalInfo)
}
}

public struct OpenIapRenewalInfo: Codable, Sendable {
Expand Down Expand Up @@ -92,4 +124,3 @@ public struct OpenIapPriceLocale: Codable, Sendable {
public let currencySymbol: String
public let countryCode: String
}

194 changes: 0 additions & 194 deletions Sources/Models/OpenIapPurchaseError.swift

This file was deleted.

Loading