@capgo/native-purchases 7.17.0 → 7.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,10 +2,6 @@ import Foundation
2
2
  import Capacitor
3
3
  import StoreKit
4
4
 
5
- /**
6
- * Please read the Capacitor iOS Plugin Development Guide
7
- * here: https://capacitorjs.com/docs/plugins/ios
8
- */
9
5
  @objc(NativePurchasesPlugin)
10
6
  public class NativePurchasesPlugin: CAPPlugin, CAPBridgedPlugin {
11
7
  public let identifier = "NativePurchasesPlugin"
@@ -20,11 +16,12 @@ public class NativePurchasesPlugin: CAPPlugin, CAPBridgedPlugin {
20
16
  CAPPluginMethod(name: "getPurchases", returnType: CAPPluginReturnPromise),
21
17
  CAPPluginMethod(name: "manageSubscriptions", returnType: CAPPluginReturnPromise),
22
18
  CAPPluginMethod(name: "acknowledgePurchase", returnType: CAPPluginReturnPromise),
19
+ CAPPluginMethod(name: "consumePurchase", returnType: CAPPluginReturnPromise),
23
20
  CAPPluginMethod(name: "getAppTransaction", returnType: CAPPluginReturnPromise),
24
21
  CAPPluginMethod(name: "isEntitledToOldBusinessModel", returnType: CAPPluginReturnPromise)
25
22
  ]
26
23
 
27
- private let pluginVersion: String = "7.17.0"
24
+ private let pluginVersion: String = "7.18.0"
28
25
  private var transactionUpdatesTask: Task<Void, Never>?
29
26
 
30
27
  @objc func getPluginVersion(_ call: CAPPluginCall) {
@@ -33,38 +30,28 @@ public class NativePurchasesPlugin: CAPPlugin, CAPBridgedPlugin {
33
30
 
34
31
  override public func load() {
35
32
  super.load()
36
- // Start listening to StoreKit transaction updates as early as possible
37
- if #available(iOS 15.0, *) {
38
- startTransactionUpdatesListener()
39
- }
33
+ startTransactionUpdatesListener()
40
34
  }
41
35
 
42
36
  deinit {
43
- if #available(iOS 15.0, *) { cancelTransactionUpdatesListener() }
44
- }
45
-
46
- private func cancelTransactionUpdatesListener() {
47
- self.transactionUpdatesTask?.cancel()
48
- self.transactionUpdatesTask = nil
37
+ transactionUpdatesTask?.cancel()
38
+ transactionUpdatesTask = nil
49
39
  }
50
40
 
51
- @available(iOS 15.0, *)
52
41
  private func startTransactionUpdatesListener() {
53
- // Ensure only one listener is running
54
- cancelTransactionUpdatesListener()
55
- let task = Task.detached { [weak self] in
42
+ transactionUpdatesTask?.cancel()
43
+ transactionUpdatesTask = Task.detached { [weak self] in
56
44
  for await result in Transaction.updates {
57
45
  guard !Task.isCancelled else { break }
58
46
  switch result {
59
47
  case .verified(let transaction):
60
- // Build payload similar to purchase response
61
- let payload = await TransactionHelpers.buildTransactionResponse(from: transaction, jwsRepresentation: result.jwsRepresentation, alwaysIncludeWillCancel: true)
62
-
63
- // Finish the transaction to avoid blocking future purchases
48
+ let payload = await TransactionHelpers.buildTransactionResponse(
49
+ from: transaction,
50
+ jwsRepresentation: result.jwsRepresentation,
51
+ alwaysIncludeWillCancel: true
52
+ )
64
53
  await transaction.finish()
65
-
66
- // Notify JS listeners on main thread, after slight delay
67
- try? await Task.sleep(nanoseconds: 500_000_000) // 0.5s delay
54
+ try? await Task.sleep(nanoseconds: 500_000_000)
68
55
  await MainActor.run {
69
56
  self?.notifyListeners("transactionUpdated", data: payload)
70
57
  }
@@ -78,93 +65,70 @@ public class NativePurchasesPlugin: CAPPlugin, CAPBridgedPlugin {
78
65
  }
79
66
  }
80
67
  }
81
- transactionUpdatesTask = task
82
68
  }
83
69
 
84
- // MARK: - Plugin Methods
85
-
86
70
  @objc func isBillingSupported(_ call: CAPPluginCall) {
87
- if #available(iOS 15, *) {
88
- call.resolve([
89
- "isBillingSupported": true
90
- ])
91
- } else {
92
- call.resolve([
93
- "isBillingSupported": false
94
- ])
95
- }
71
+ call.resolve(["isBillingSupported": true])
96
72
  }
97
73
 
98
74
  @objc func purchaseProduct(_ call: CAPPluginCall) {
99
- if #available(iOS 15, *) {
100
- print("purchaseProduct")
101
- let productIdentifier = call.getString("productIdentifier", "")
102
- let quantity = call.getInt("quantity", 1)
103
- let appAccountToken = call.getString("appAccountToken")
104
- let autoAcknowledge = call.getBool("autoAcknowledgePurchases") ?? true
105
-
106
- if productIdentifier.isEmpty {
107
- call.reject("productIdentifier is Empty, give an id")
108
- return
109
- }
110
-
111
- print("Auto-acknowledge enabled: \(autoAcknowledge)")
112
-
113
- Task { @MainActor in
114
- do {
115
- let products = try await Product.products(for: [productIdentifier])
116
- guard let product = products.first else {
117
- call.reject("Cannot find product for id \(productIdentifier)")
118
- return
119
- }
75
+ print("purchaseProduct")
76
+ let productIdentifier = call.getString("productIdentifier", "")
77
+ let quantity = call.getInt("quantity", 1)
78
+ let appAccountToken = call.getString("appAccountToken")
79
+ let autoAcknowledge = call.getBool("autoAcknowledgePurchases") ?? true
80
+
81
+ if productIdentifier.isEmpty {
82
+ call.reject("productIdentifier is Empty, give an id")
83
+ return
84
+ }
120
85
 
121
- let purchaseOptions = self.buildPurchaseOptions(quantity: quantity, appAccountToken: appAccountToken)
122
- let result = try await product.purchase(options: purchaseOptions)
123
- print("purchaseProduct result \(result)")
86
+ print("Auto-acknowledge enabled: \(autoAcknowledge)")
124
87
 
125
- await self.handlePurchaseResult(result, call: call, autoFinish: autoAcknowledge)
126
- } catch {
127
- print(error)
128
- call.reject(error.localizedDescription)
88
+ Task { @MainActor in
89
+ do {
90
+ let products = try await Product.products(for: [productIdentifier])
91
+ guard let product = products.first else {
92
+ call.reject("Cannot find product for id \(productIdentifier)")
93
+ return
129
94
  }
130
- }
131
- } else {
132
- print("Not implemented under ios 15")
133
- call.reject("Not implemented under ios 15")
134
- }
135
- }
136
95
 
137
- @available(iOS 15.0, *)
138
- private func buildPurchaseOptions(quantity: Int, appAccountToken: String?) -> Set<Product.PurchaseOption> {
139
- var purchaseOptions = Set<Product.PurchaseOption>()
140
- purchaseOptions.insert(Product.PurchaseOption.quantity(quantity))
96
+ var purchaseOptions = Set<Product.PurchaseOption>()
97
+ purchaseOptions.insert(.quantity(quantity))
98
+ if let token = appAccountToken, !token.isEmpty, let uuid = UUID(uuidString: token) {
99
+ purchaseOptions.insert(.appAccountToken(uuid))
100
+ }
141
101
 
142
- if let accountToken = appAccountToken, !accountToken.isEmpty, let tokenData = UUID(uuidString: accountToken) {
143
- purchaseOptions.insert(Product.PurchaseOption.appAccountToken(tokenData))
102
+ let result = try await product.purchase(options: purchaseOptions)
103
+ print("purchaseProduct result \(result)")
104
+ await self.handlePurchaseResult(result, call: call, autoFinish: autoAcknowledge)
105
+ } catch {
106
+ print(error)
107
+ call.reject(error.localizedDescription)
108
+ }
144
109
  }
145
-
146
- return purchaseOptions
147
110
  }
148
111
 
149
- @available(iOS 15.0, *)
150
112
  @MainActor
151
- private func handlePurchaseResult(_ result: Product.PurchaseResult, call: CAPPluginCall, autoFinish: Bool) async {
113
+ private func handlePurchaseResult(
114
+ _ result: Product.PurchaseResult,
115
+ call: CAPPluginCall,
116
+ autoFinish: Bool
117
+ ) async {
152
118
  switch result {
153
119
  case let .success(verificationResult):
154
120
  switch verificationResult {
155
121
  case .verified(let transaction):
156
- let response = await TransactionHelpers.buildTransactionResponse(from: transaction, jwsRepresentation: verificationResult.jwsRepresentation)
157
-
122
+ let response = await TransactionHelpers.buildTransactionResponse(
123
+ from: transaction,
124
+ jwsRepresentation: verificationResult.jwsRepresentation
125
+ )
158
126
  if autoFinish {
159
127
  print("Auto-finishing transaction: \(transaction.id)")
160
128
  await transaction.finish()
161
129
  } else {
162
130
  print("Manual finish required for transaction: \(transaction.id)")
163
- print("Transaction will remain unfinished until acknowledgePurchase() is called")
164
- // Don't finish - transaction remains in StoreKit's queue
165
- // Can be retrieved later via Transaction.all
166
131
  }
167
-
168
132
  call.resolve(response)
169
133
  case .unverified(_, let error):
170
134
  call.reject(error.localizedDescription)
@@ -179,237 +143,144 @@ public class NativePurchasesPlugin: CAPPlugin, CAPBridgedPlugin {
179
143
  }
180
144
 
181
145
  @objc func restorePurchases(_ call: CAPPluginCall) {
182
- if #available(iOS 15.0, *) {
183
- print("restorePurchases")
184
- Task {
185
- do {
186
- try await AppStore.sync()
187
- // make finish() calls for all transactions and consume all consumables
188
- for transaction in SKPaymentQueue.default().transactions {
189
- SKPaymentQueue.default().finishTransaction(transaction)
190
- }
191
- await MainActor.run {
192
- call.resolve()
193
- }
194
- } catch {
195
- await MainActor.run {
196
- call.reject(error.localizedDescription)
197
- }
146
+ print("restorePurchases")
147
+ Task {
148
+ do {
149
+ try await AppStore.sync()
150
+ for transaction in SKPaymentQueue.default().transactions {
151
+ SKPaymentQueue.default().finishTransaction(transaction)
198
152
  }
153
+ await MainActor.run { call.resolve() }
154
+ } catch {
155
+ await MainActor.run { call.reject(error.localizedDescription) }
199
156
  }
200
- } else {
201
- print("Not implemented under ios 15")
202
- call.reject("Not implemented under ios 15")
203
157
  }
204
158
  }
205
159
 
206
160
  @objc func getProducts(_ call: CAPPluginCall) {
207
- if #available(iOS 15.0, *) {
208
- let productIdentifiers = call.getArray("productIdentifiers", String.self) ?? []
209
- print("productIdentifiers \(productIdentifiers)")
210
- Task {
211
- do {
212
- let products = try await Product.products(for: productIdentifiers)
213
- print("products \(products)")
214
- let productsJson: [[String: Any]] = products.map { $0.dictionary }
215
- await MainActor.run {
216
- call.resolve([
217
- "products": productsJson
218
- ])
219
- }
220
- } catch {
221
- print("error \(error)")
222
- await MainActor.run {
223
- call.reject(error.localizedDescription)
224
- }
225
- }
161
+ let productIdentifiers = call.getArray("productIdentifiers", String.self) ?? []
162
+ let productType = call.getString("productType", "inapp")
163
+ print("productIdentifiers \(productIdentifiers)")
164
+ print("productType \(productType)")
165
+ Task {
166
+ do {
167
+ let products = try await Product.products(for: productIdentifiers)
168
+ print("products \(products)")
169
+ let productsJson: [[String: Any]] = products.map { $0.dictionary }
170
+ await MainActor.run { call.resolve(["products": productsJson]) }
171
+ } catch {
172
+ print("error \(error)")
173
+ await MainActor.run { call.reject(error.localizedDescription) }
226
174
  }
227
- } else {
228
- print("Not implemented under ios 15")
229
- call.reject("Not implemented under ios 15")
230
175
  }
231
176
  }
232
177
 
233
178
  @objc func getProduct(_ call: CAPPluginCall) {
234
- if #available(iOS 15.0, *) {
235
- let productIdentifier = call.getString("productIdentifier") ?? ""
236
- print("productIdentifier \(productIdentifier)")
237
- if productIdentifier.isEmpty {
238
- call.reject("productIdentifier is empty")
239
- return
240
- }
179
+ let productIdentifier = call.getString("productIdentifier") ?? ""
180
+ let productType = call.getString("productType", "inapp")
181
+ print("productIdentifier \(productIdentifier)")
182
+ print("productType \(productType)")
183
+ if productIdentifier.isEmpty {
184
+ call.reject("productIdentifier is empty")
185
+ return
186
+ }
241
187
 
242
- Task {
243
- do {
244
- let products = try await Product.products(for: [productIdentifier])
245
- print("products \(products)")
246
- if let product = products.first {
247
- let productJson = product.dictionary
248
- await MainActor.run {
249
- call.resolve(["product": productJson])
250
- }
251
- } else {
252
- await MainActor.run {
253
- call.reject("Product not found")
254
- }
255
- }
256
- } catch {
257
- print(error)
258
- await MainActor.run {
259
- call.reject(error.localizedDescription)
260
- }
188
+ Task {
189
+ do {
190
+ let products = try await Product.products(for: [productIdentifier])
191
+ print("products \(products)")
192
+ if let product = products.first {
193
+ await MainActor.run { call.resolve(["product": product.dictionary]) }
194
+ } else {
195
+ await MainActor.run { call.reject("Product not found") }
261
196
  }
197
+ } catch {
198
+ print(error)
199
+ await MainActor.run { call.reject(error.localizedDescription) }
262
200
  }
263
- } else {
264
- print("Not implemented under iOS 15")
265
- call.reject("Not implemented under iOS 15")
266
201
  }
267
202
  }
268
203
 
269
204
  @objc func getPurchases(_ call: CAPPluginCall) {
205
+ print("getPurchases")
270
206
  let appAccountTokenFilter = call.getString("appAccountToken")
271
- if #available(iOS 15.0, *) {
272
- print("getPurchases")
273
- Task {
274
- let allPurchases = await TransactionHelpers.collectAllPurchases(appAccountTokenFilter: appAccountTokenFilter)
275
- await MainActor.run {
276
- call.resolve(["purchases": allPurchases])
277
- }
278
- }
279
- } else {
280
- print("Not implemented under iOS 15")
281
- call.reject("Not implemented under iOS 15")
207
+ Task {
208
+ let allPurchases = await TransactionHelpers.collectAllPurchases(
209
+ appAccountTokenFilter: appAccountTokenFilter
210
+ )
211
+ await MainActor.run { call.resolve(["purchases": allPurchases]) }
282
212
  }
283
213
  }
284
214
 
285
215
  @objc func manageSubscriptions(_ call: CAPPluginCall) {
286
- if #available(iOS 15.0, *) {
287
- print("manageSubscriptions")
288
- Task { @MainActor in
289
- do {
290
- // Get the current window scene
291
- guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene else {
292
- call.reject("Unable to get window scene")
293
- return
294
- }
295
- // Open the App Store subscription management page
296
- try await AppStore.showManageSubscriptions(in: windowScene)
297
- call.resolve()
298
- } catch {
299
- print("manageSubscriptions error: \(error)")
300
- call.reject(error.localizedDescription)
216
+ print("manageSubscriptions")
217
+ Task { @MainActor in
218
+ do {
219
+ guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene else {
220
+ call.reject("Unable to get window scene")
221
+ return
301
222
  }
223
+ try await AppStore.showManageSubscriptions(in: windowScene)
224
+ call.resolve()
225
+ } catch {
226
+ print("manageSubscriptions error: \(error)")
227
+ call.reject(error.localizedDescription)
302
228
  }
303
- } else {
304
- print("Not implemented under iOS 15")
305
- call.reject("Not implemented under iOS 15")
306
229
  }
307
230
  }
308
231
 
309
232
  @objc func acknowledgePurchase(_ call: CAPPluginCall) {
310
- if #available(iOS 15.0, *) {
311
- print("acknowledgePurchase called on iOS")
233
+ print("acknowledgePurchase called on iOS")
312
234
 
313
- guard let purchaseToken = call.getString("purchaseToken") else {
314
- call.reject("purchaseToken is required")
315
- return
316
- }
317
-
318
- // On iOS, purchaseToken is the transactionId (UInt64 as string)
319
- guard let transactionId = UInt64(purchaseToken) else {
320
- call.reject("Invalid purchaseToken format")
321
- return
322
- }
235
+ guard let purchaseToken = call.getString("purchaseToken") else {
236
+ call.reject("purchaseToken is required")
237
+ return
238
+ }
323
239
 
324
- Task {
325
- // Search for the transaction in StoreKit's unfinished transactions
326
- // This works even after app restart because StoreKit persists them
327
- var foundTransaction: Transaction?
328
-
329
- for await verificationResult in Transaction.all {
330
- switch verificationResult {
331
- case .verified(let transaction):
332
- if transaction.id == transactionId {
333
- foundTransaction = transaction
334
- break
335
- }
336
- case .unverified:
337
- continue
338
- }
339
- if foundTransaction != nil {
340
- break
341
- }
342
- }
240
+ guard let transactionId = UInt64(purchaseToken) else {
241
+ call.reject("Invalid purchaseToken format")
242
+ return
243
+ }
343
244
 
344
- guard let transaction = foundTransaction else {
345
- await MainActor.run {
346
- call.reject("Transaction not found or already finished. Transaction ID: \(transactionId)")
347
- }
348
- return
245
+ Task {
246
+ var foundTransaction: Transaction?
247
+ for await verificationResult in Transaction.all {
248
+ if case .verified(let transaction) = verificationResult, transaction.id == transactionId {
249
+ foundTransaction = transaction
250
+ break
349
251
  }
252
+ }
350
253
 
351
- print("Manually finishing transaction: \(transaction.id)")
352
- await transaction.finish()
353
-
254
+ guard let transaction = foundTransaction else {
354
255
  await MainActor.run {
355
- print("Transaction finished successfully")
356
- call.resolve()
256
+ call.reject("Transaction not found or already finished. Transaction ID: \(transactionId)")
357
257
  }
258
+ return
259
+ }
260
+
261
+ print("Manually finishing transaction: \(transaction.id)")
262
+ await transaction.finish()
263
+ await MainActor.run {
264
+ print("Transaction finished successfully")
265
+ call.resolve()
358
266
  }
359
- } else {
360
- call.reject("Not implemented under iOS 15")
361
267
  }
362
268
  }
363
269
 
270
+ @objc func consumePurchase(_ call: CAPPluginCall) {
271
+ call.reject("consumePurchase is only available on Android")
272
+ }
273
+
274
+ }
275
+
276
+ // MARK: - iOS 16+ App Transaction Methods
277
+ extension NativePurchasesPlugin {
364
278
  @objc func getAppTransaction(_ call: CAPPluginCall) {
365
279
  if #available(iOS 16.0, *) {
366
- print("getAppTransaction called on iOS")
367
280
  Task { @MainActor in
368
- do {
369
- let verificationResult = try await AppTransaction.shared
370
- switch verificationResult {
371
- case .verified(let appTransaction):
372
- var response: [String: Any] = [:]
373
-
374
- // originalAppVersion is the CFBundleVersion (build number) at the time of original download
375
- response["originalAppVersion"] = appTransaction.originalAppVersion
376
-
377
- // Original purchase date
378
- response["originalPurchaseDate"] = ISO8601DateFormatter().string(from: appTransaction.originalPurchaseDate)
379
-
380
- // Bundle ID
381
- response["bundleId"] = appTransaction.bundleID
382
-
383
- // Current app version (build number)
384
- response["appVersion"] = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? ""
385
-
386
- // Environment
387
- switch appTransaction.environment {
388
- case .sandbox:
389
- response["environment"] = "Sandbox"
390
- case .production:
391
- response["environment"] = "Production"
392
- case .xcode:
393
- response["environment"] = "Xcode"
394
- default:
395
- response["environment"] = "Production"
396
- }
397
-
398
- // JWS representation for server-side verification
399
- response["jwsRepresentation"] = verificationResult.jwsRepresentation
400
-
401
- call.resolve(["appTransaction": response])
402
-
403
- case .unverified(_, let error):
404
- call.reject("App transaction verification failed: \(error.localizedDescription)")
405
- }
406
- } catch {
407
- print("getAppTransaction error: \(error)")
408
- call.reject("Failed to get app transaction: \(error.localizedDescription)")
409
- }
281
+ await self.handleGetAppTransaction(call)
410
282
  }
411
283
  } else {
412
- print("getAppTransaction not implemented under iOS 16")
413
284
  call.reject("App Transaction requires iOS 16.0 or later")
414
285
  }
415
286
  }
@@ -421,44 +292,78 @@ public class NativePurchasesPlugin: CAPPlugin, CAPBridgedPlugin {
421
292
  }
422
293
 
423
294
  if #available(iOS 16.0, *) {
424
- print("isEntitledToOldBusinessModel called with targetBuildNumber: \(targetBuildNumber)")
425
295
  Task { @MainActor in
426
- do {
427
- let verificationResult = try await AppTransaction.shared
428
- switch verificationResult {
429
- case .verified(let appTransaction):
430
- let originalBuildNumber = appTransaction.originalAppVersion
431
-
432
- // Compare build numbers (integers)
433
- let isOlder = self.compareVersions(originalBuildNumber, targetBuildNumber) < 0
434
-
435
- call.resolve([
436
- "isOlderVersion": isOlder,
437
- "originalAppVersion": originalBuildNumber
438
- ])
439
-
440
- case .unverified(_, let error):
441
- call.reject("App transaction verification failed: \(error.localizedDescription)")
442
- }
443
- } catch {
444
- print("isEntitledToOldBusinessModel error: \(error)")
445
- call.reject("Failed to get app transaction: \(error.localizedDescription)")
446
- }
296
+ await self.handleIsEntitledToOldBusinessModel(call, targetBuildNumber: targetBuildNumber)
447
297
  }
448
298
  } else {
449
- print("isEntitledToOldBusinessModel not implemented under iOS 16")
450
299
  call.reject("App Transaction requires iOS 16.0 or later")
451
300
  }
452
301
  }
453
302
 
454
- // MARK: - Version Comparison Helper
303
+ @available(iOS 16.0, *)
304
+ @MainActor
305
+ private func handleGetAppTransaction(_ call: CAPPluginCall) async {
306
+ print("getAppTransaction called on iOS")
307
+ do {
308
+ let verificationResult = try await AppTransaction.shared
309
+ switch verificationResult {
310
+ case .verified(let appTransaction):
311
+ let response: [String: Any] = [
312
+ "originalAppVersion": appTransaction.originalAppVersion,
313
+ "originalPurchaseDate": ISO8601DateFormatter().string(
314
+ from: appTransaction.originalPurchaseDate
315
+ ),
316
+ "bundleId": appTransaction.bundleID,
317
+ "appVersion": Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "",
318
+ "jwsRepresentation": verificationResult.jwsRepresentation,
319
+ "environment": appTransaction.environment.environmentString
320
+ ]
321
+ call.resolve(["appTransaction": response])
322
+ case .unverified(_, let error):
323
+ call.reject("App transaction verification failed: \(error.localizedDescription)")
324
+ }
325
+ } catch {
326
+ print("getAppTransaction error: \(error)")
327
+ call.reject("Failed to get app transaction: \(error.localizedDescription)")
328
+ }
329
+ }
455
330
 
456
- /// Compares two build numbers as integers.
457
- /// Returns: negative if v1 < v2, zero if v1 == v2, positive if v1 > v2
458
- private func compareVersions(_ version1: String, _ version2: String) -> Int {
459
- let v1Int = Int(version1) ?? 0
460
- let v2Int = Int(version2) ?? 0
461
- return v1Int - v2Int
331
+ @available(iOS 16.0, *)
332
+ @MainActor
333
+ private func handleIsEntitledToOldBusinessModel(
334
+ _ call: CAPPluginCall,
335
+ targetBuildNumber: String
336
+ ) async {
337
+ print("isEntitledToOldBusinessModel called with targetBuildNumber: \(targetBuildNumber)")
338
+ do {
339
+ let verificationResult = try await AppTransaction.shared
340
+ switch verificationResult {
341
+ case .verified(let appTransaction):
342
+ let originalBuildNumber = appTransaction.originalAppVersion
343
+ let originalInt = Int(originalBuildNumber) ?? 0
344
+ let targetInt = Int(targetBuildNumber) ?? 0
345
+ call.resolve([
346
+ "isOlderVersion": originalInt < targetInt,
347
+ "originalAppVersion": originalBuildNumber
348
+ ])
349
+ case .unverified(_, let error):
350
+ call.reject("App transaction verification failed: \(error.localizedDescription)")
351
+ }
352
+ } catch {
353
+ print("isEntitledToOldBusinessModel error: \(error)")
354
+ call.reject("Failed to get app transaction: \(error.localizedDescription)")
355
+ }
462
356
  }
357
+ }
463
358
 
359
+ @available(iOS 16.0, *)
360
+ private extension AppStore.Environment {
361
+ var environmentString: String {
362
+ switch self {
363
+ case .sandbox: return "Sandbox"
364
+ case .production: return "Production"
365
+ case .xcode: return "Xcode"
366
+ default: return "Production"
367
+ }
368
+ }
464
369
  }
@@ -8,7 +8,6 @@
8
8
  import Foundation
9
9
  import StoreKit
10
10
 
11
- @available(iOS 15.0, *)
12
11
  extension Product {
13
12
 
14
13
  var dictionary: [String: Any] {