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

Commit 81bc2d8

Browse files
authored
feat: apply unified error + init coalescing (#6)
### Overview - Unifies error handling and simplifies initialization. ### Error Handling - Consolidates error event payload into **OpenIapError** - Provides default messages - Uses a single `E_*` code source - Adds `errorCodes` / `errorMessages` for bridging ### Enum Changes - Rename thrown enum to **OpenIapFailure** - Update all listeners and examples accordingly
1 parent 88711fc commit 81bc2d8

File tree

9 files changed

+123
-192
lines changed

9 files changed

+123
-192
lines changed

Example/OpenIapExample/Screens/PurchaseFlowScreen.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ struct PurchaseFlowScreen: View {
282282
}
283283
}
284284

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

288288
// Update UI state

Example/OpenIapExample/Screens/SubscriptionFlowScreen.swift

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

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

Example/OpenIapExample/ViewModels/TransactionObserver.swift

Lines changed: 1 addition & 1 deletion
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: OpenIapErrorEvent) {
52+
private func handlePurchaseError(_ error: OpenIapError) {
5353
print("❌ Purchase failed - Code: \(error.code), Message: \(error.message)")
5454
errorMessage = error.message
5555
isPending = false

Sources/Models/OpenIapSerialization.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ public enum OpenIapSerialization {
115115
let codes = OpenIapError.errorCodes().values
116116
var map: [String: String] = [:]
117117
for code in codes {
118-
map[code] = OpenIapErrorEvent.defaultMessage(for: code)
118+
map[code] = OpenIapError.defaultMessage(for: code)
119119
}
120120
return map
121121
}

Sources/OpenIapError.swift

Lines changed: 53 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import Foundation
22

3-
public enum OpenIapError: LocalizedError {
3+
public enum OpenIapFailure: LocalizedError {
44
case productNotFound(id: String)
55
case purchaseFailed(reason: String)
66
case purchaseCancelled
@@ -44,10 +44,21 @@ public enum OpenIapError: LocalizedError {
4444
}
4545
}
4646

47-
// MARK: - Error Code Constants + Mapping (for bridging)
47+
// MARK: - Unified Error Event + Codes
48+
public struct OpenIapError: Codable, Equatable {
49+
public let code: String
50+
public let message: String
51+
public let productId: String?
52+
53+
public init(code: String, message: String, productId: String? = nil) {
54+
self.code = code
55+
self.message = message
56+
self.productId = productId
57+
}
58+
}
59+
4860
public extension OpenIapError {
49-
// Expose the same string codes available on PurchaseError, for convenience
50-
// and to avoid importing two symbols at call sites.
61+
// Error Code Constants (single source)
5162
static let E_UNKNOWN = "E_UNKNOWN"
5263
static let E_SERVICE_ERROR = "E_SERVICE_ERROR"
5364
static let E_USER_CANCELLED = "E_USER_CANCELLED"
@@ -83,36 +94,6 @@ public extension OpenIapError {
8394
static let E_ITEM_NOT_OWNED = "E_ITEM_NOT_OWNED"
8495
static let E_EMPTY_SKU_LIST = "E_EMPTY_SKU_LIST"
8596

86-
/// OpenIAP string code that corresponds to this error case
87-
var code: String {
88-
switch self {
89-
case .purchaseCancelled:
90-
return Self.E_USER_CANCELLED
91-
case .purchaseDeferred:
92-
return Self.E_DEFERRED_PAYMENT
93-
case .productNotFound:
94-
return Self.E_SKU_NOT_FOUND
95-
case .purchaseFailed:
96-
return Self.E_SERVICE_ERROR
97-
case .paymentNotAllowed:
98-
return Self.E_IAP_NOT_AVAILABLE
99-
case .invalidReceipt:
100-
return Self.E_RECEIPT_FAILED
101-
case .networkError:
102-
return Self.E_NETWORK_ERROR
103-
case .verificationFailed:
104-
return Self.E_TRANSACTION_VALIDATION_FAILED
105-
case .restoreFailed:
106-
return Self.E_SERVICE_ERROR
107-
case .storeKitError:
108-
return Self.E_SERVICE_ERROR
109-
case .notSupported:
110-
return Self.E_FEATURE_NOT_SUPPORTED
111-
case .unknownError:
112-
return Self.E_UNKNOWN
113-
}
114-
}
115-
11697
/// Dictionary of error keys to OpenIAP codes
11798
static func errorCodes() -> [String: String] {
11899
return [
@@ -168,55 +149,7 @@ public extension OpenIapError {
168149
}
169150
}
170151

171-
// MARK: - Unified Error Event under OpenIapError namespace
172-
public struct OpenIapErrorEvent: Codable, Equatable {
173-
public let code: String
174-
public let message: String
175-
public let productId: String?
176-
177-
public init(code: String, message: String, productId: String? = nil) {
178-
self.code = code
179-
self.message = message
180-
self.productId = productId
181-
}
182-
183-
// Re-export error code constants (single source under OpenIapError)
184-
public static let E_USER_CANCELLED = OpenIapError.E_USER_CANCELLED
185-
public static let E_USER_ERROR = OpenIapError.E_USER_ERROR
186-
public static let E_DEFERRED_PAYMENT = OpenIapError.E_DEFERRED_PAYMENT
187-
public static let E_INTERRUPTED = OpenIapError.E_INTERRUPTED
188-
public static let E_ITEM_UNAVAILABLE = OpenIapError.E_ITEM_UNAVAILABLE
189-
public static let E_SKU_NOT_FOUND = OpenIapError.E_SKU_NOT_FOUND
190-
public static let E_SKU_OFFER_MISMATCH = OpenIapError.E_SKU_OFFER_MISMATCH
191-
public static let E_QUERY_PRODUCT = OpenIapError.E_QUERY_PRODUCT
192-
public static let E_ALREADY_OWNED = OpenIapError.E_ALREADY_OWNED
193-
public static let E_ITEM_NOT_OWNED = OpenIapError.E_ITEM_NOT_OWNED
194-
public static let E_NETWORK_ERROR = OpenIapError.E_NETWORK_ERROR
195-
public static let E_SERVICE_ERROR = OpenIapError.E_SERVICE_ERROR
196-
public static let E_REMOTE_ERROR = OpenIapError.E_REMOTE_ERROR
197-
public static let E_INIT_CONNECTION = OpenIapError.E_INIT_CONNECTION
198-
public static let E_SERVICE_DISCONNECTED = OpenIapError.E_SERVICE_DISCONNECTED
199-
public static let E_CONNECTION_CLOSED = OpenIapError.E_CONNECTION_CLOSED
200-
public static let E_IAP_NOT_AVAILABLE = OpenIapError.E_IAP_NOT_AVAILABLE
201-
public static let E_BILLING_UNAVAILABLE = OpenIapError.E_BILLING_UNAVAILABLE
202-
public static let E_FEATURE_NOT_SUPPORTED = OpenIapError.E_FEATURE_NOT_SUPPORTED
203-
public static let E_SYNC_ERROR = OpenIapError.E_SYNC_ERROR
204-
public static let E_RECEIPT_FAILED = OpenIapError.E_RECEIPT_FAILED
205-
public static let E_RECEIPT_FINISHED = OpenIapError.E_RECEIPT_FINISHED
206-
public static let E_RECEIPT_FINISHED_FAILED = OpenIapError.E_RECEIPT_FINISHED_FAILED
207-
public static let E_TRANSACTION_VALIDATION_FAILED = OpenIapError.E_TRANSACTION_VALIDATION_FAILED
208-
public static let E_EMPTY_SKU_LIST = OpenIapError.E_EMPTY_SKU_LIST
209-
public static let E_NOT_PREPARED = OpenIapError.E_NOT_PREPARED
210-
public static let E_NOT_ENDED = OpenIapError.E_NOT_ENDED
211-
public static let E_DEVELOPER_ERROR = OpenIapError.E_DEVELOPER_ERROR
212-
public static let E_PURCHASE_ERROR = OpenIapError.E_PURCHASE_ERROR
213-
public static let E_ACTIVITY_UNAVAILABLE = OpenIapError.E_ACTIVITY_UNAVAILABLE
214-
public static let E_ALREADY_PREPARED = OpenIapError.E_ALREADY_PREPARED
215-
public static let E_PENDING = OpenIapError.E_PENDING
216-
public static let E_UNKNOWN = OpenIapError.E_UNKNOWN
217-
}
218-
219-
public extension OpenIapErrorEvent {
152+
public extension OpenIapError {
220153
/// Default human-readable message for a given error code
221154
static func defaultMessage(for code: String) -> String {
222155
switch code {
@@ -269,21 +202,21 @@ public extension OpenIapErrorEvent {
269202
}
270203

271204
/// Convenience factory that fills a default message for the code
272-
static func make(code: String, productId: String? = nil, message: String? = nil) -> OpenIapErrorEvent {
273-
return OpenIapErrorEvent(
205+
static func make(code: String, productId: String? = nil, message: String? = nil) -> OpenIapError {
206+
return OpenIapError(
274207
code: code,
275208
message: message ?? defaultMessage(for: code),
276209
productId: productId
277210
)
278211
}
279212

280213
/// Convenience: create error for empty SKU list (parity with previous API)
281-
static func emptySkuList() -> OpenIapErrorEvent {
282-
return OpenIapErrorEvent(code: OpenIapError.E_EMPTY_SKU_LIST, message: "Empty SKU list provided")
214+
static func emptySkuList() -> OpenIapError {
215+
return OpenIapError(code: OpenIapError.E_EMPTY_SKU_LIST, message: "Empty SKU list provided")
283216
}
284217

285218
/// Create from OpenIapError
286-
init(from error: OpenIapError, productId: String? = nil) {
219+
init(from error: OpenIapFailure, productId: String? = nil) {
287220
switch error {
288221
case .purchaseCancelled:
289222
self.init(code: Self.E_USER_CANCELLED, message: "User cancelled the purchase flow", productId: productId)
@@ -346,5 +279,35 @@ public extension OpenIapErrorEvent {
346279
}
347280
}
348281

349-
// Backward compatibility: keep previous public type names
350-
// (Removed backward-compat typealiases as requested)
282+
// MARK: - Mapping from thrown errors to codes
283+
public extension OpenIapFailure {
284+
/// OpenIAP string code that corresponds to this error case
285+
var code: String {
286+
switch self {
287+
case .purchaseCancelled:
288+
return OpenIapError.E_USER_CANCELLED
289+
case .purchaseDeferred:
290+
return OpenIapError.E_DEFERRED_PAYMENT
291+
case .productNotFound:
292+
return OpenIapError.E_SKU_NOT_FOUND
293+
case .purchaseFailed:
294+
return OpenIapError.E_SERVICE_ERROR
295+
case .paymentNotAllowed:
296+
return OpenIapError.E_IAP_NOT_AVAILABLE
297+
case .invalidReceipt:
298+
return OpenIapError.E_RECEIPT_FAILED
299+
case .networkError:
300+
return OpenIapError.E_NETWORK_ERROR
301+
case .verificationFailed:
302+
return OpenIapError.E_TRANSACTION_VALIDATION_FAILED
303+
case .restoreFailed:
304+
return OpenIapError.E_SERVICE_ERROR
305+
case .storeKitError:
306+
return OpenIapError.E_SERVICE_ERROR
307+
case .notSupported:
308+
return OpenIapError.E_FEATURE_NOT_SUPPORTED
309+
case .unknownError:
310+
return OpenIapError.E_UNKNOWN
311+
}
312+
}
313+
}

0 commit comments

Comments
 (0)