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

Commit 648bcb5

Browse files
committed
feat: Add Objective-C bridge for Kotlin Multiplatform with OpenIapSerialization
- Add @objc extension methods with completion handler wrappers for all OpenIAP APIs - Use OpenIapSerialization helpers for type conversions - Convert Product/Purchase enums to dictionaries for Kotlin interop - Support for products, purchases, subscriptions, and transactions - Enable seamless Kotlin/Native integration via cinterop
1 parent 9a99e5d commit 648bcb5

File tree

1 file changed

+282
-0
lines changed

1 file changed

+282
-0
lines changed

Sources/OpenIapModule+ObjC.swift

Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
import Foundation
2+
import StoreKit
3+
4+
// MARK: - Objective-C Bridge for Kotlin Multiplatform
5+
6+
@available(iOS 15.0, macOS 14.0, *)
7+
@objc public extension OpenIapModule {
8+
9+
// MARK: - Connection Management
10+
11+
@objc func initConnectionWithCompletion(_ completion: @escaping (Bool, Error?) -> Void) {
12+
Task {
13+
do {
14+
let result = try await initConnection()
15+
completion(result, nil)
16+
} catch {
17+
completion(false, error)
18+
}
19+
}
20+
}
21+
22+
@objc func endConnectionWithCompletion(_ completion: @escaping (Bool, Error?) -> Void) {
23+
Task {
24+
do {
25+
let result = try await endConnection()
26+
completion(result, nil)
27+
} catch {
28+
completion(false, error)
29+
}
30+
}
31+
}
32+
33+
// MARK: - Product Management
34+
35+
@objc func fetchProductsWithSkus(
36+
_ skus: [String],
37+
type: String?,
38+
completion: @escaping ([Any]?, Error?) -> Void
39+
) {
40+
Task {
41+
do {
42+
let productType = type.flatMap { ProductQueryType(rawValue: $0) }
43+
let request = ProductRequest(skus: skus, type: productType)
44+
let result = try await fetchProducts(request)
45+
46+
switch result {
47+
case .products(let products):
48+
// Extract ProductIOS from Product enum and convert to dictionaries
49+
let productIOS = (products ?? []).compactMap { product -> ProductIOS? in
50+
guard case let .productIos(value) = product else { return nil }
51+
return value
52+
}
53+
print("[OpenIAP] Fetched \(productIOS.count) products")
54+
let dictionaries = productIOS.map { OpenIapSerialization.encode($0) }
55+
completion(dictionaries, nil)
56+
57+
case .subscriptions(let subscriptions):
58+
// Extract ProductSubscriptionIOS from ProductSubscription enum and convert to dictionaries
59+
let subscriptionIOS = (subscriptions ?? []).compactMap { subscription -> ProductSubscriptionIOS? in
60+
guard case let .productSubscriptionIos(value) = subscription else { return nil }
61+
return value
62+
}
63+
print("[OpenIAP] Fetched \(subscriptionIOS.count) subscriptions")
64+
let dictionaries = subscriptionIOS.map { OpenIapSerialization.encode($0) }
65+
completion(dictionaries, nil)
66+
}
67+
} catch {
68+
completion(nil, error)
69+
}
70+
}
71+
}
72+
73+
@objc func getPromotedProductIOSWithCompletion(_ completion: @escaping (Any?, Error?) -> Void) {
74+
Task {
75+
do {
76+
let product = try await getPromotedProductIOS()
77+
if let productIOS = product {
78+
// Convert ProductIOS to dictionary
79+
let dictionary = OpenIapSerialization.encode(productIOS)
80+
completion(dictionary, nil)
81+
} else {
82+
completion(nil, nil)
83+
}
84+
} catch {
85+
completion(nil, error)
86+
}
87+
}
88+
}
89+
90+
// MARK: - Purchase Management
91+
92+
@objc func requestPurchaseWithSku(
93+
_ sku: String,
94+
quantity: Int,
95+
type: String?,
96+
completion: @escaping (Any?, Error?) -> Void
97+
) {
98+
Task {
99+
do {
100+
let productType = type.flatMap { ProductQueryType(rawValue: $0) } ?? .inApp
101+
let iosProps = RequestPurchaseIosProps(
102+
andDangerouslyFinishTransactionAutomatically: nil,
103+
appAccountToken: nil,
104+
quantity: quantity,
105+
sku: sku,
106+
withOffer: nil
107+
)
108+
let props = RequestPurchaseProps(
109+
request: .purchase(
110+
RequestPurchasePropsByPlatforms(android: nil, ios: iosProps)
111+
),
112+
type: productType
113+
)
114+
115+
let result = try await requestPurchase(props)
116+
117+
switch result {
118+
case .purchase(let purchase):
119+
if let purchase = purchase {
120+
let dictionary = OpenIapSerialization.purchase(purchase)
121+
completion(dictionary, nil)
122+
} else {
123+
completion(nil, nil)
124+
}
125+
case .purchases(let purchases):
126+
if let firstPurchase = purchases?.first {
127+
let dictionary = OpenIapSerialization.purchase(firstPurchase)
128+
completion(dictionary, nil)
129+
} else {
130+
completion(nil, nil)
131+
}
132+
case .none:
133+
completion(nil, nil)
134+
}
135+
} catch {
136+
completion(nil, error)
137+
}
138+
}
139+
}
140+
141+
@objc func restorePurchasesWithCompletion(_ completion: @escaping (Error?) -> Void) {
142+
Task {
143+
do {
144+
try await restorePurchases()
145+
completion(nil)
146+
} catch {
147+
completion(error)
148+
}
149+
}
150+
}
151+
152+
@objc func getAvailablePurchasesWithCompletion(_ completion: @escaping ([Any]?, Error?) -> Void) {
153+
Task {
154+
do {
155+
let purchases = try await getAvailablePurchases(nil)
156+
let dictionaries = OpenIapSerialization.purchases(purchases)
157+
completion(dictionaries, nil)
158+
} catch {
159+
completion(nil, error)
160+
}
161+
}
162+
}
163+
164+
// MARK: - Transaction Management
165+
166+
@objc func finishTransactionWithPurchaseId(
167+
_ purchaseId: String,
168+
productId: String,
169+
isConsumable: Bool,
170+
completion: @escaping (Error?) -> Void
171+
) {
172+
Task {
173+
do {
174+
let purchaseInput = PurchaseInput(
175+
id: purchaseId,
176+
ids: nil,
177+
isAutoRenewing: false,
178+
platform: .ios,
179+
productId: productId,
180+
purchaseState: .purchased,
181+
purchaseToken: nil,
182+
quantity: 1,
183+
transactionDate: Date().timeIntervalSince1970
184+
)
185+
try await finishTransaction(purchase: purchaseInput, isConsumable: isConsumable)
186+
completion(nil)
187+
} catch {
188+
completion(error)
189+
}
190+
}
191+
}
192+
193+
@objc func getPendingTransactionsIOSWithCompletion(_ completion: @escaping ([Any]?, Error?) -> Void) {
194+
Task {
195+
do {
196+
let transactions = try await getPendingTransactionsIOS()
197+
// Convert [PurchaseIOS] to dictionaries directly
198+
let dictionaries = transactions.map { OpenIapSerialization.encode($0) }
199+
completion(dictionaries, nil)
200+
} catch {
201+
completion(nil, error)
202+
}
203+
}
204+
}
205+
206+
@objc func clearTransactionIOSWithCompletion(_ completion: @escaping (Bool, Error?) -> Void) {
207+
Task {
208+
do {
209+
let result = try await clearTransactionIOS()
210+
completion(result, nil)
211+
} catch {
212+
completion(false, error)
213+
}
214+
}
215+
}
216+
217+
// MARK: - Validation
218+
219+
@objc func getReceiptDataIOSWithCompletion(_ completion: @escaping (String?, Error?) -> Void) {
220+
Task {
221+
do {
222+
let receipt = try await getReceiptDataIOS()
223+
completion(receipt, nil)
224+
} catch {
225+
completion(nil, error)
226+
}
227+
}
228+
}
229+
230+
// MARK: - Store Information
231+
232+
@objc func getStorefrontIOSWithCompletion(_ completion: @escaping (String?, Error?) -> Void) {
233+
Task {
234+
do {
235+
let storefront = try await getStorefrontIOS()
236+
completion(storefront, nil)
237+
} catch {
238+
completion(nil, error)
239+
}
240+
}
241+
}
242+
243+
// MARK: - Subscription Management
244+
245+
@objc func getActiveSubscriptionsWithCompletion(_ completion: @escaping ([Any]?, Error?) -> Void) {
246+
Task {
247+
do {
248+
let subscriptions = try await getActiveSubscriptions(nil)
249+
let dictionaries = subscriptions.map { OpenIapSerialization.encode($0) }
250+
completion(dictionaries, nil)
251+
} catch {
252+
completion(nil, error)
253+
}
254+
}
255+
}
256+
257+
// MARK: - UI
258+
259+
@objc func presentCodeRedemptionSheetIOSWithCompletion(_ completion: @escaping (Bool, Error?) -> Void) {
260+
Task {
261+
do {
262+
let result = try await presentCodeRedemptionSheetIOS()
263+
completion(result, nil)
264+
} catch {
265+
completion(false, error)
266+
}
267+
}
268+
}
269+
270+
@objc func showManageSubscriptionsIOSWithCompletion(_ completion: @escaping ([Any]?, Error?) -> Void) {
271+
Task {
272+
do {
273+
let purchases = try await showManageSubscriptionsIOS()
274+
// Convert [PurchaseIOS] to dictionaries directly
275+
let dictionaries = purchases.map { OpenIapSerialization.encode($0) }
276+
completion(dictionaries, nil)
277+
} catch {
278+
completion(nil, error)
279+
}
280+
}
281+
}
282+
}

0 commit comments

Comments
 (0)