Skip to content
This repository was archived by the owner on Oct 16, 2025. It is now read-only.

Commit 16a92d6

Browse files
authored
feat: apply renewalstate, error messages, md rules (#5)
Switch subscription status to StoreKit RenewalState and keep JSON compatibility by encoding the enum as a string. Add default messages for all error codes and expose an errorMessages() map for bridging. Also fill missing error code mappings and disable markdownlint MD013/MD033 for the docs.
1 parent 1e17508 commit 16a92d6

12 files changed

+324
-273
lines changed

.markdownlint.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"default": true,
3+
"MD013": false,
4+
"line-length": false,
5+
"MD033": false,
6+
"no-inline-html": false
7+
}

Example/OpenIapExample/Screens/PurchaseFlowScreen.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -282,15 +282,15 @@ struct PurchaseFlowScreen: View {
282282
}
283283
}
284284

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

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

292292
// Show error alert for non-cancellation errors
293-
if error.code != PurchaseError.E_USER_CANCELLED {
293+
if error.code != OpenIapError.E_USER_CANCELLED {
294294
errorMessage = error.message
295295
showError = true
296296
}

Example/OpenIapExample/Screens/SubscriptionFlowScreen.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -324,9 +324,8 @@ struct SubscriptionFlowScreen: View {
324324
}
325325
}
326326

327-
private func handlePurchaseError(_ error: PurchaseError) {
327+
private func handlePurchaseError(_ error: OpenIapErrorEvent) {
328328
print("❌ [SubscriptionFlow] Subscription error: \(error.message)")
329329
// Error status is already handled internally by OpenIapStore
330330
}
331331
}
332-

Example/OpenIapExample/ViewModels/TransactionObserver.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ class TransactionObserver: ObservableObject {
4949
errorMessage = nil
5050
}
5151

52-
private func handlePurchaseError(_ error: PurchaseError) {
52+
private func handlePurchaseError(_ error: OpenIapErrorEvent) {
5353
print("❌ Purchase failed - Code: \(error.code), Message: \(error.message)")
5454
errorMessage = error.message
5555
isPending = false
@@ -96,4 +96,4 @@ struct TransactionObserverExampleView: View {
9696
formatter.timeStyle = .short
9797
return formatter
9898
}
99-
}
99+
}

README.md

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ import OpenIAP
109109
@MainActor
110110
class StoreViewModel: ObservableObject {
111111
private let iapProvider: OpenIapProvider
112-
112+
113113
init() {
114114
// Setup provider with event handlers
115115
self.iapProvider = OpenIapProvider(
@@ -120,19 +120,19 @@ class StoreViewModel: ObservableObject {
120120
print("Purchase failed: \(error.message)")
121121
}
122122
)
123-
123+
124124
Task {
125125
// Initialize connection
126126
try await iapProvider.initConnection()
127-
127+
128128
// Fetch products
129129
try await iapProvider.fetchProducts(
130130
skus: ["product1", "product2"],
131131
type: .inapp
132132
)
133133
}
134134
}
135-
135+
136136
deinit {
137137
Task {
138138
// End connection when done
@@ -152,22 +152,22 @@ import OpenIAP
152152
@MainActor
153153
func setupStore() async throws {
154154
let module = OpenIapModule.shared
155-
155+
156156
// Initialize connection first
157157
_ = try await module.initConnection()
158-
158+
159159
// Setup listeners
160160
let subscription = module.purchaseUpdatedListener { purchase in
161161
print("Purchase updated: \(purchase.productId)")
162162
}
163-
163+
164164
// Fetch and purchase
165165
let request = ProductRequest(skus: ["premium"], type: .all)
166166
let products = try await module.fetchProducts(request)
167-
167+
168168
let props = RequestPurchaseProps(sku: "premium")
169169
let purchase = try await module.requestPurchase(props)
170-
170+
171171
// When done, clean up
172172
module.removeListener(subscription)
173173
_ = try await module.endConnection()
@@ -179,7 +179,9 @@ func setupStore() async throws {
179179
OpenIAP now has a **simplified, minimal API** with just 2 main components:
180180

181181
### Core Components
182-
1. **OpenIapModule** (`OpenIapModule.swift`)
182+
183+
1. **OpenIapModule** (`OpenIapModule.swift`)
184+
183185
- Core StoreKit 2 implementation
184186
- Static convenience methods for simple usage
185187
- Low-level instance methods for advanced control
@@ -191,6 +193,7 @@ OpenIAP now has a **simplified, minimal API** with just 2 main components:
191193
- Perfect for MVVM architecture
192194

193195
### Why This Design?
196+
194197
- **No Duplication**: Each component has a distinct purpose
195198
- **Flexibility**: Use global functions, static methods, or instances
196199
- **Simplicity**: Only 2 files to understand instead of 4+
@@ -266,17 +269,17 @@ The library provides explicit connection management with automatic listener clea
266269
```swift
267270
class StoreViewModel: ObservableObject {
268271
private let iapProvider = OpenIapProvider()
269-
272+
270273
init() {
271274
Task {
272275
// Initialize connection
273276
try await iapProvider.initConnection()
274-
277+
275278
// Fetch products
276279
try await iapProvider.fetchProducts(skus: productIds)
277280
}
278281
}
279-
282+
280283
deinit {
281284
Task {
282285
// End connection (listeners cleaned up automatically)

Sources/Models/OpenIapAppTransaction.swift

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,40 @@ extension OpenIapAppTransaction {
5151
}
5252

5353
public struct OpenIapSubscriptionStatus: Codable, Sendable {
54-
public let state: String
54+
public let state: Product.SubscriptionInfo.RenewalState
5555
public let renewalInfo: OpenIapRenewalInfo?
56+
57+
enum CodingKeys: String, CodingKey {
58+
case state
59+
case renewalInfo
60+
}
61+
62+
public init(state: Product.SubscriptionInfo.RenewalState, renewalInfo: OpenIapRenewalInfo?) {
63+
self.state = state
64+
self.renewalInfo = renewalInfo
65+
}
66+
67+
// Encode enum as string for JSON compatibility
68+
public func encode(to encoder: Encoder) throws {
69+
var container = encoder.container(keyedBy: CodingKeys.self)
70+
try container.encode(String(describing: state), forKey: .state)
71+
try container.encodeIfPresent(renewalInfo, forKey: .renewalInfo)
72+
}
73+
74+
// Decode state from string into known cases; fallback to .expired
75+
public init(from decoder: Decoder) throws {
76+
let container = try decoder.container(keyedBy: CodingKeys.self)
77+
let raw = (try? container.decode(String.self, forKey: .state))?.lowercased()
78+
switch raw {
79+
case "subscribed": self.state = .subscribed
80+
case "expired": self.state = .expired
81+
case "inbillingretryperiod": self.state = .inBillingRetryPeriod
82+
case "ingraceperiod": self.state = .inGracePeriod
83+
case "revoked": self.state = .revoked
84+
default: self.state = .expired
85+
}
86+
self.renewalInfo = try container.decodeIfPresent(OpenIapRenewalInfo.self, forKey: .renewalInfo)
87+
}
5688
}
5789

5890
public struct OpenIapRenewalInfo: Codable, Sendable {
@@ -92,4 +124,3 @@ public struct OpenIapPriceLocale: Codable, Sendable {
92124
public let currencySymbol: String
93125
public let countryCode: String
94126
}
95-

Sources/Models/OpenIapPurchaseError.swift

Lines changed: 0 additions & 194 deletions
This file was deleted.

0 commit comments

Comments
 (0)