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

Commit c1f7c9e

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 c1f7c9e

File tree

1 file changed

+276
-0
lines changed

1 file changed

+276
-0
lines changed

Sources/OpenIapModule+ObjC.swift

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
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+
let dictionary = OpenIapSerialization.purchase(purchase)
120+
completion(dictionary, nil)
121+
case .purchases(let purchases):
122+
if let firstPurchase = purchases?.first {
123+
let dictionary = OpenIapSerialization.purchase(firstPurchase)
124+
completion(dictionary, nil)
125+
} else {
126+
completion(nil, nil)
127+
}
128+
case .none:
129+
completion(nil, nil)
130+
}
131+
} catch {
132+
completion(nil, error)
133+
}
134+
}
135+
}
136+
137+
@objc func restorePurchasesWithCompletion(_ completion: @escaping (Error?) -> Void) {
138+
Task {
139+
do {
140+
try await restorePurchases()
141+
completion(nil)
142+
} catch {
143+
completion(error)
144+
}
145+
}
146+
}
147+
148+
@objc func getAvailablePurchasesWithCompletion(_ completion: @escaping ([Any]?, Error?) -> Void) {
149+
Task {
150+
do {
151+
let purchases = try await getAvailablePurchases(nil)
152+
let dictionaries = OpenIapSerialization.purchases(purchases)
153+
completion(dictionaries, nil)
154+
} catch {
155+
completion(nil, error)
156+
}
157+
}
158+
}
159+
160+
// MARK: - Transaction Management
161+
162+
@objc func finishTransactionWithPurchaseId(
163+
_ purchaseId: String,
164+
productId: String,
165+
isConsumable: Bool,
166+
completion: @escaping (Error?) -> Void
167+
) {
168+
Task {
169+
do {
170+
let purchaseInput = PurchaseInput(
171+
id: purchaseId,
172+
ids: nil,
173+
isAutoRenewing: false,
174+
platform: .ios,
175+
productId: productId,
176+
purchaseState: .purchased,
177+
purchaseToken: nil,
178+
quantity: 1,
179+
transactionDate: Date().timeIntervalSince1970
180+
)
181+
try await finishTransaction(purchase: purchaseInput, isConsumable: isConsumable)
182+
completion(nil)
183+
} catch {
184+
completion(error)
185+
}
186+
}
187+
}
188+
189+
@objc func getPendingTransactionsIOSWithCompletion(_ completion: @escaping ([Any]?, Error?) -> Void) {
190+
Task {
191+
do {
192+
let transactions = try await getPendingTransactionsIOS()
193+
let dictionaries = OpenIapSerialization.purchases(transactions)
194+
completion(dictionaries, nil)
195+
} catch {
196+
completion(nil, error)
197+
}
198+
}
199+
}
200+
201+
@objc func clearTransactionIOSWithCompletion(_ completion: @escaping (Bool, Error?) -> Void) {
202+
Task {
203+
do {
204+
let result = try await clearTransactionIOS()
205+
completion(result, nil)
206+
} catch {
207+
completion(false, error)
208+
}
209+
}
210+
}
211+
212+
// MARK: - Validation
213+
214+
@objc func getReceiptDataIOSWithCompletion(_ completion: @escaping (String?, Error?) -> Void) {
215+
Task {
216+
do {
217+
let receipt = try await getReceiptDataIOS()
218+
completion(receipt, nil)
219+
} catch {
220+
completion(nil, error)
221+
}
222+
}
223+
}
224+
225+
// MARK: - Store Information
226+
227+
@objc func getStorefrontIOSWithCompletion(_ completion: @escaping (String?, Error?) -> Void) {
228+
Task {
229+
do {
230+
let storefront = try await getStorefrontIOS()
231+
completion(storefront, nil)
232+
} catch {
233+
completion(nil, error)
234+
}
235+
}
236+
}
237+
238+
// MARK: - Subscription Management
239+
240+
@objc func getActiveSubscriptionsWithCompletion(_ completion: @escaping ([Any]?, Error?) -> Void) {
241+
Task {
242+
do {
243+
let subscriptions = try await getActiveSubscriptions(nil)
244+
let dictionaries = subscriptions.map { OpenIapSerialization.encode($0) }
245+
completion(dictionaries, nil)
246+
} catch {
247+
completion(nil, error)
248+
}
249+
}
250+
}
251+
252+
// MARK: - UI
253+
254+
@objc func presentCodeRedemptionSheetIOSWithCompletion(_ completion: @escaping (Bool, Error?) -> Void) {
255+
Task {
256+
do {
257+
let result = try await presentCodeRedemptionSheetIOS()
258+
completion(result, nil)
259+
} catch {
260+
completion(false, error)
261+
}
262+
}
263+
}
264+
265+
@objc func showManageSubscriptionsIOSWithCompletion(_ completion: @escaping ([Any]?, Error?) -> Void) {
266+
Task {
267+
do {
268+
let purchases = try await showManageSubscriptionsIOS()
269+
let dictionaries = OpenIapSerialization.purchases(purchases)
270+
completion(dictionaries, nil)
271+
} catch {
272+
completion(nil, error)
273+
}
274+
}
275+
}
276+
}

0 commit comments

Comments
 (0)