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

Commit ddf60f8

Browse files
committed
refactor: rename Swift files and update Example with detailed purchase event logging
- Rename IapModule.swift to OpenIapModule.swift - Rename IapError.swift to OpenIapError.swift - Fix iOS availability issues in Purchase.swift for StoreKit 2 APIs - Update Package.swift test target name from OpenIAPTests to OpenIapTests - Add comprehensive purchase event logging to Example app StoreViewModel - Fix OpenIapPurchase initializers in tests with new iOS properties - Update Example app type references from IapPurchase to OpenIapPurchase
1 parent 4ba7c2a commit ddf60f8

File tree

6 files changed

+138
-21
lines changed

6 files changed

+138
-21
lines changed

Example/OpenIapExample/Screens/AvailablePurchasesScreen.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -448,7 +448,7 @@ struct SectionHeaderView: View {
448448

449449
// MARK: - Product List Card
450450
struct ProductListCard: View {
451-
let product: IapProductData
451+
let product: OpenIapProductData
452452
let onPurchase: () -> Void
453453

454454
var body: some View {
@@ -525,7 +525,7 @@ struct ProductListCard: View {
525525

526526
// MARK: - Product Grid Card (Deprecated)
527527
struct ProductGridCard: View {
528-
let product: IapProductData
528+
let product: OpenIapProductData
529529
let onPurchase: () -> Void
530530

531531
var body: some View {

Example/OpenIapExample/Screens/PurchaseFlowScreen.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ struct ProductCard: View {
171171
}
172172

173173
struct RecentPurchasesSection: View {
174-
let purchases: [IapPurchase]
174+
let purchases: [OpenIapPurchase]
175175

176176
var body: some View {
177177
VStack(alignment: .leading, spacing: 16) {
@@ -225,7 +225,7 @@ struct RecentPurchasesSection: View {
225225
}
226226

227227
struct RecentPurchaseRow: View {
228-
let purchase: IapPurchase
228+
let purchase: OpenIapPurchase
229229

230230
var body: some View {
231231
HStack {

Example/OpenIapExample/ViewModels/StoreViewModel.swift

Lines changed: 84 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,82 @@ class StoreViewModel: ObservableObject {
1717
private let iapModule = OpenIapModule.shared
1818

1919
init() {
20+
print("🚀 StoreViewModel Initializing...")
2021
setupStoreKit()
2122
}
2223

24+
deinit {
25+
print("🧹 StoreViewModel Deinitializing - cleaning up listeners...")
26+
iapModule.removeAllPurchaseUpdatedListeners()
27+
iapModule.removeAllPurchaseErrorListeners()
28+
}
29+
2330
private func setupStoreKit() {
2431
Task {
2532
do {
2633
_ = try await iapModule.initConnection()
34+
35+
// Setup purchase event listeners
36+
setupPurchaseListeners()
37+
2738
await MainActor.run {
2839
isConnectionInitialized = true
2940
}
41+
print("🔧 StoreKit initialized and purchase listeners setup")
3042
} catch {
3143
showErrorMessage("Failed to initialize StoreKit: \(error.localizedDescription)")
3244
}
3345
}
3446
}
3547

48+
private func setupPurchaseListeners() {
49+
// Add purchase updated listener
50+
iapModule.addPurchaseUpdatedListener { [weak self] purchase in
51+
Task { @MainActor in
52+
print("🎯 Purchase Updated Event Received:")
53+
print(" • Product ID: \(purchase.productId)")
54+
print(" • Transaction ID: \(purchase.transactionId)")
55+
print(" • Purchase State: \(purchase.purchaseState)")
56+
print(" • Purchase Time: \(purchase.purchaseTime)")
57+
print(" • Is Auto Renewing: \(purchase.isAutoRenewing)")
58+
print(" • Acknowledgement State: \(purchase.acknowledgementState)")
59+
60+
self?.handlePurchaseUpdated(purchase)
61+
}
62+
}
63+
64+
// Add purchase error listener
65+
iapModule.addPurchaseErrorListener { [weak self] error in
66+
Task { @MainActor in
67+
print("💥 Purchase Error Event Received:")
68+
print(" • Error: \(error)")
69+
print(" • Description: \(error.localizedDescription)")
70+
71+
self?.handlePurchaseError(error, productId: nil)
72+
}
73+
}
74+
75+
print("👂 Purchase event listeners configured")
76+
}
77+
78+
private func handlePurchaseUpdated(_ purchase: OpenIapPurchase) {
79+
print("🔄 Processing purchase update for: \(purchase.productId)")
80+
81+
switch purchase.purchaseState {
82+
case .purchased:
83+
handlePurchaseSuccess(purchase.productId)
84+
case .failed:
85+
handlePurchaseError(OpenIapError.purchaseFailed(reason: "Purchase failed"), productId: purchase.productId)
86+
case .pending:
87+
print("⏳ Purchase pending for: \(purchase.productId)")
88+
case .restored:
89+
print("♻️ Purchase restored for: \(purchase.productId)")
90+
handlePurchaseSuccess(purchase.productId)
91+
case .deferred:
92+
print("⏸️ Purchase deferred for: \(purchase.productId)")
93+
}
94+
}
95+
3696
private func handlePurchaseSuccess(_ productId: String) {
3797
print("✅ Purchase successful: \(productId)")
3898

@@ -53,10 +113,14 @@ class StoreViewModel: ObservableObject {
53113
}
54114

55115
private func handlePurchaseError(_ error: Error, productId: String?) {
56-
print("❌ Purchase error: \(error.localizedDescription)")
116+
print("❌ Purchase Error Handler Called:")
117+
print(" • Error Type: \(type(of: error))")
118+
print(" • Error Description: \(error.localizedDescription)")
119+
print(" • Product ID: \(productId ?? "N/A")")
57120

58121
// Remove loading state for this product if available
59122
if let productId = productId {
123+
print(" • Removing loading state for product: \(productId)")
60124
purchasingProductIds.remove(productId)
61125
}
62126

@@ -113,10 +177,15 @@ class StoreViewModel: ObservableObject {
113177
// Start loading state for this specific product
114178
purchasingProductIds.insert(product.id)
115179

116-
print("🛒 Starting purchase request for: \(product.title)")
180+
print("🛒 Purchase Process Started:")
181+
print(" • Product ID: \(product.id)")
182+
print(" • Product Title: \(product.title)")
183+
print(" • Product Price: \(product.displayPrice)")
184+
print(" • Product Type: \(product.type)")
117185

118186
Task {
119187
do {
188+
print("🔄 Calling requestPurchase API...")
120189
let transactionData = try await iapModule.requestPurchase(
121190
sku: product.id,
122191
andDangerouslyFinishTransactionAutomatically: true,
@@ -125,19 +194,27 @@ class StoreViewModel: ObservableObject {
125194
discountOffer: nil
126195
)
127196

128-
if let _ = transactionData {
129-
print("✅ Purchase successful: \(product.title)")
197+
print("📦 Purchase API Response:")
198+
if let transaction = transactionData {
199+
print(" • Transaction received: \(transaction.transactionId)")
200+
print(" • Product ID: \(transaction.productId)")
201+
print(" • Purchase State: \(transaction.purchaseState)")
202+
print("✅ Purchase successful via API: \(product.title)")
130203
await MainActor.run {
131204
handlePurchaseSuccess(product.id)
132205
}
133206
} else {
134-
print("❌ Purchase failed")
207+
print(" • No transaction data received")
208+
print("❌ Purchase failed: No transaction data")
135209
await MainActor.run {
136-
handlePurchaseError(OpenIapError.purchaseFailed(reason: "Unknown error"), productId: product.id)
210+
handlePurchaseError(OpenIapError.purchaseFailed(reason: "No transaction data received"), productId: product.id)
137211
}
138212
}
139213
} catch {
140-
print("❌ Purchase error: \(error.localizedDescription)")
214+
print("💥 Purchase API Error:")
215+
print(" • Error Type: \(type(of: error))")
216+
print(" • Error Description: \(error.localizedDescription)")
217+
print(" • Product ID: \(product.id)")
141218
await MainActor.run {
142219
handlePurchaseError(error, productId: product.id)
143220
}

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ let package = Package(
2222
dependencies: [],
2323
path: "Sources"),
2424
.testTarget(
25-
name: "OpenIAPTests",
25+
name: "OpenIapTests",
2626
dependencies: ["OpenIAP"],
2727
path: "Tests"),
2828
],

Sources/Models/Purchase.swift

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,13 @@ extension OpenIapPurchase {
113113

114114
// iOS StoreKit 2 additional properties
115115
self.webOrderLineItemIdIOS = Int(transaction.webOrderLineItemID ?? "0")
116-
self.environmentIOS = transaction.environment.rawValue
116+
117+
// Environment (iOS 16.0+)
118+
if #available(iOS 16.0, macOS 14.0, *) {
119+
self.environmentIOS = transaction.environment.rawValue
120+
} else {
121+
self.environmentIOS = nil
122+
}
117123
self.storefrontCountryCodeIOS = transaction.storefrontCountryCode
118124
self.appBundleIdIOS = transaction.appBundleID
119125
self.subscriptionGroupIdIOS = transaction.subscriptionGroupID
@@ -147,8 +153,8 @@ extension OpenIapPurchase {
147153
self.ownershipTypeIOS = "purchased"
148154
}
149155

150-
// Reason and revocation
151-
if #available(iOS 15.4, macOS 14.0, *) {
156+
// Reason and revocation (iOS 17.0+)
157+
if #available(iOS 17.0, macOS 14.0, *) {
152158
switch transaction.reason {
153159
case .purchase:
154160
self.reasonIOS = "purchase"
@@ -173,8 +179,8 @@ extension OpenIapPurchase {
173179
self.revocationReasonIOS = nil
174180
}
175181

176-
// Offer information (promotional offers)
177-
if #available(iOS 15.4, macOS 14.2, *) {
182+
// Offer information (promotional offers, iOS 17.2+)
183+
if #available(iOS 17.2, macOS 14.2, *) {
178184
if let offer = transaction.offer {
179185
self.offerIOS = PurchaseOffer(
180186
id: offer.id ?? "unknown",

Tests/OpenIapTests.swift

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,29 @@ final class OpenIapTests: XCTestCase {
4343
expiryTime: Date().addingTimeInterval(86400 * 30),
4444
isAutoRenewing: true,
4545
purchaseState: .purchased,
46-
developerPayload: nil,
4746
acknowledgementState: .acknowledged,
4847
quantity: 1,
48+
developerPayload: nil,
4949
jwsRepresentation: nil,
5050
jsonRepresentation: nil,
51-
appAccountToken: nil
51+
appAccountToken: nil,
52+
webOrderLineItemIdIOS: nil,
53+
environmentIOS: "Production",
54+
storefrontCountryCodeIOS: "US",
55+
appBundleIdIOS: "dev.hyo.martie",
56+
productTypeIOS: "auto_renewable_subscription",
57+
subscriptionGroupIdIOS: "group1",
58+
isUpgradedIOS: false,
59+
ownershipTypeIOS: "purchased",
60+
reasonIOS: "purchase",
61+
reasonStringRepresentationIOS: "purchase",
62+
transactionReasonIOS: "PURCHASE",
63+
revocationDateIOS: nil,
64+
revocationReasonIOS: nil,
65+
offerIOS: nil,
66+
currencyCodeIOS: "USD",
67+
currencySymbolIOS: "$",
68+
countryCodeIOS: "US"
5269
)
5370

5471
XCTAssertEqual(purchase.productId, "dev.hyo.premium")
@@ -107,12 +124,29 @@ final class OpenIapTests: XCTestCase {
107124
expiryTime: nil,
108125
isAutoRenewing: false,
109126
purchaseState: .purchased,
110-
developerPayload: nil,
111127
acknowledgementState: .acknowledged,
112128
quantity: 1,
129+
developerPayload: nil,
113130
jwsRepresentation: nil,
114131
jsonRepresentation: nil,
115-
appAccountToken: nil
132+
appAccountToken: nil,
133+
webOrderLineItemIdIOS: nil,
134+
environmentIOS: "Production",
135+
storefrontCountryCodeIOS: "US",
136+
appBundleIdIOS: "dev.hyo.app",
137+
productTypeIOS: "consumable",
138+
subscriptionGroupIdIOS: nil,
139+
isUpgradedIOS: false,
140+
ownershipTypeIOS: "purchased",
141+
reasonIOS: "purchase",
142+
reasonStringRepresentationIOS: "purchase",
143+
transactionReasonIOS: "PURCHASE",
144+
revocationDateIOS: nil,
145+
revocationReasonIOS: nil,
146+
offerIOS: nil,
147+
currencyCodeIOS: "USD",
148+
currencySymbolIOS: "$",
149+
countryCodeIOS: "US"
116150
)
117151
]
118152

0 commit comments

Comments
 (0)