@capgo/native-purchases 7.18.0-alpha.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.
- package/README.md +293 -48
- package/android/build.gradle +2 -2
- package/android/src/main/java/ee/forgr/nativepurchases/NativePurchasesPlugin.java +148 -77
- package/dist/docs.json +37 -4
- package/dist/esm/definitions.d.ts +76 -0
- package/dist/esm/definitions.js.map +1 -1
- package/ios/Sources/NativePurchasesPlugin/NativePurchasesPlugin.swift +204 -304
- package/ios/Sources/NativePurchasesPlugin/Product+CapacitorPurchasesPlugin.swift +0 -1
- package/ios/Sources/NativePurchasesPlugin/TransactionHelpers.swift +85 -129
- package/package.json +1 -1
|
@@ -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"
|
|
@@ -25,7 +21,7 @@ public class NativePurchasesPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
25
21
|
CAPPluginMethod(name: "isEntitledToOldBusinessModel", returnType: CAPPluginReturnPromise)
|
|
26
22
|
]
|
|
27
23
|
|
|
28
|
-
private let pluginVersion: String = "7.18.0
|
|
24
|
+
private let pluginVersion: String = "7.18.0"
|
|
29
25
|
private var transactionUpdatesTask: Task<Void, Never>?
|
|
30
26
|
|
|
31
27
|
@objc func getPluginVersion(_ call: CAPPluginCall) {
|
|
@@ -34,38 +30,28 @@ public class NativePurchasesPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
34
30
|
|
|
35
31
|
override public func load() {
|
|
36
32
|
super.load()
|
|
37
|
-
|
|
38
|
-
if #available(iOS 15.0, *) {
|
|
39
|
-
startTransactionUpdatesListener()
|
|
40
|
-
}
|
|
33
|
+
startTransactionUpdatesListener()
|
|
41
34
|
}
|
|
42
35
|
|
|
43
36
|
deinit {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
private func cancelTransactionUpdatesListener() {
|
|
48
|
-
self.transactionUpdatesTask?.cancel()
|
|
49
|
-
self.transactionUpdatesTask = nil
|
|
37
|
+
transactionUpdatesTask?.cancel()
|
|
38
|
+
transactionUpdatesTask = nil
|
|
50
39
|
}
|
|
51
40
|
|
|
52
|
-
@available(iOS 15.0, *)
|
|
53
41
|
private func startTransactionUpdatesListener() {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
let task = Task.detached { [weak self] in
|
|
42
|
+
transactionUpdatesTask?.cancel()
|
|
43
|
+
transactionUpdatesTask = Task.detached { [weak self] in
|
|
57
44
|
for await result in Transaction.updates {
|
|
58
45
|
guard !Task.isCancelled else { break }
|
|
59
46
|
switch result {
|
|
60
47
|
case .verified(let transaction):
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
48
|
+
let payload = await TransactionHelpers.buildTransactionResponse(
|
|
49
|
+
from: transaction,
|
|
50
|
+
jwsRepresentation: result.jwsRepresentation,
|
|
51
|
+
alwaysIncludeWillCancel: true
|
|
52
|
+
)
|
|
65
53
|
await transaction.finish()
|
|
66
|
-
|
|
67
|
-
// Notify JS listeners on main thread, after slight delay
|
|
68
|
-
try? await Task.sleep(nanoseconds: 500_000_000) // 0.5s delay
|
|
54
|
+
try? await Task.sleep(nanoseconds: 500_000_000)
|
|
69
55
|
await MainActor.run {
|
|
70
56
|
self?.notifyListeners("transactionUpdated", data: payload)
|
|
71
57
|
}
|
|
@@ -79,93 +65,70 @@ public class NativePurchasesPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
79
65
|
}
|
|
80
66
|
}
|
|
81
67
|
}
|
|
82
|
-
transactionUpdatesTask = task
|
|
83
68
|
}
|
|
84
69
|
|
|
85
|
-
// MARK: - Plugin Methods
|
|
86
|
-
|
|
87
70
|
@objc func isBillingSupported(_ call: CAPPluginCall) {
|
|
88
|
-
|
|
89
|
-
call.resolve([
|
|
90
|
-
"isBillingSupported": true
|
|
91
|
-
])
|
|
92
|
-
} else {
|
|
93
|
-
call.resolve([
|
|
94
|
-
"isBillingSupported": false
|
|
95
|
-
])
|
|
96
|
-
}
|
|
71
|
+
call.resolve(["isBillingSupported": true])
|
|
97
72
|
}
|
|
98
73
|
|
|
99
74
|
@objc func purchaseProduct(_ call: CAPPluginCall) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
print("Auto-acknowledge enabled: \(autoAcknowledge)")
|
|
113
|
-
|
|
114
|
-
Task { @MainActor in
|
|
115
|
-
do {
|
|
116
|
-
let products = try await Product.products(for: [productIdentifier])
|
|
117
|
-
guard let product = products.first else {
|
|
118
|
-
call.reject("Cannot find product for id \(productIdentifier)")
|
|
119
|
-
return
|
|
120
|
-
}
|
|
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
|
+
}
|
|
121
85
|
|
|
122
|
-
|
|
123
|
-
let result = try await product.purchase(options: purchaseOptions)
|
|
124
|
-
print("purchaseProduct result \(result)")
|
|
86
|
+
print("Auto-acknowledge enabled: \(autoAcknowledge)")
|
|
125
87
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
|
130
94
|
}
|
|
131
|
-
}
|
|
132
|
-
} else {
|
|
133
|
-
print("Not implemented under ios 15")
|
|
134
|
-
call.reject("Not implemented under ios 15")
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
95
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
+
}
|
|
142
101
|
|
|
143
|
-
|
|
144
|
-
|
|
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
|
+
}
|
|
145
109
|
}
|
|
146
|
-
|
|
147
|
-
return purchaseOptions
|
|
148
110
|
}
|
|
149
111
|
|
|
150
|
-
@available(iOS 15.0, *)
|
|
151
112
|
@MainActor
|
|
152
|
-
private func handlePurchaseResult(
|
|
113
|
+
private func handlePurchaseResult(
|
|
114
|
+
_ result: Product.PurchaseResult,
|
|
115
|
+
call: CAPPluginCall,
|
|
116
|
+
autoFinish: Bool
|
|
117
|
+
) async {
|
|
153
118
|
switch result {
|
|
154
119
|
case let .success(verificationResult):
|
|
155
120
|
switch verificationResult {
|
|
156
121
|
case .verified(let transaction):
|
|
157
|
-
let response = await TransactionHelpers.buildTransactionResponse(
|
|
158
|
-
|
|
122
|
+
let response = await TransactionHelpers.buildTransactionResponse(
|
|
123
|
+
from: transaction,
|
|
124
|
+
jwsRepresentation: verificationResult.jwsRepresentation
|
|
125
|
+
)
|
|
159
126
|
if autoFinish {
|
|
160
127
|
print("Auto-finishing transaction: \(transaction.id)")
|
|
161
128
|
await transaction.finish()
|
|
162
129
|
} else {
|
|
163
130
|
print("Manual finish required for transaction: \(transaction.id)")
|
|
164
|
-
print("Transaction will remain unfinished until acknowledgePurchase() is called")
|
|
165
|
-
// Don't finish - transaction remains in StoreKit's queue
|
|
166
|
-
// Can be retrieved later via Transaction.all
|
|
167
131
|
}
|
|
168
|
-
|
|
169
132
|
call.resolve(response)
|
|
170
133
|
case .unverified(_, let error):
|
|
171
134
|
call.reject(error.localizedDescription)
|
|
@@ -180,185 +143,127 @@ public class NativePurchasesPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
180
143
|
}
|
|
181
144
|
|
|
182
145
|
@objc func restorePurchases(_ call: CAPPluginCall) {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
for transaction in SKPaymentQueue.default().transactions {
|
|
190
|
-
SKPaymentQueue.default().finishTransaction(transaction)
|
|
191
|
-
}
|
|
192
|
-
await MainActor.run {
|
|
193
|
-
call.resolve()
|
|
194
|
-
}
|
|
195
|
-
} catch {
|
|
196
|
-
await MainActor.run {
|
|
197
|
-
call.reject(error.localizedDescription)
|
|
198
|
-
}
|
|
146
|
+
print("restorePurchases")
|
|
147
|
+
Task {
|
|
148
|
+
do {
|
|
149
|
+
try await AppStore.sync()
|
|
150
|
+
for transaction in SKPaymentQueue.default().transactions {
|
|
151
|
+
SKPaymentQueue.default().finishTransaction(transaction)
|
|
199
152
|
}
|
|
153
|
+
await MainActor.run { call.resolve() }
|
|
154
|
+
} catch {
|
|
155
|
+
await MainActor.run { call.reject(error.localizedDescription) }
|
|
200
156
|
}
|
|
201
|
-
} else {
|
|
202
|
-
print("Not implemented under ios 15")
|
|
203
|
-
call.reject("Not implemented under ios 15")
|
|
204
157
|
}
|
|
205
158
|
}
|
|
206
159
|
|
|
207
160
|
@objc func getProducts(_ call: CAPPluginCall) {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
} catch {
|
|
222
|
-
print("error \(error)")
|
|
223
|
-
await MainActor.run {
|
|
224
|
-
call.reject(error.localizedDescription)
|
|
225
|
-
}
|
|
226
|
-
}
|
|
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) }
|
|
227
174
|
}
|
|
228
|
-
} else {
|
|
229
|
-
print("Not implemented under ios 15")
|
|
230
|
-
call.reject("Not implemented under ios 15")
|
|
231
175
|
}
|
|
232
176
|
}
|
|
233
177
|
|
|
234
178
|
@objc func getProduct(_ call: CAPPluginCall) {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
+
}
|
|
242
187
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
}
|
|
252
|
-
} else {
|
|
253
|
-
await MainActor.run {
|
|
254
|
-
call.reject("Product not found")
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
} catch {
|
|
258
|
-
print(error)
|
|
259
|
-
await MainActor.run {
|
|
260
|
-
call.reject(error.localizedDescription)
|
|
261
|
-
}
|
|
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") }
|
|
262
196
|
}
|
|
197
|
+
} catch {
|
|
198
|
+
print(error)
|
|
199
|
+
await MainActor.run { call.reject(error.localizedDescription) }
|
|
263
200
|
}
|
|
264
|
-
} else {
|
|
265
|
-
print("Not implemented under iOS 15")
|
|
266
|
-
call.reject("Not implemented under iOS 15")
|
|
267
201
|
}
|
|
268
202
|
}
|
|
269
203
|
|
|
270
204
|
@objc func getPurchases(_ call: CAPPluginCall) {
|
|
205
|
+
print("getPurchases")
|
|
271
206
|
let appAccountTokenFilter = call.getString("appAccountToken")
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
call.resolve(["purchases": allPurchases])
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
} else {
|
|
281
|
-
print("Not implemented under iOS 15")
|
|
282
|
-
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]) }
|
|
283
212
|
}
|
|
284
213
|
}
|
|
285
214
|
|
|
286
215
|
@objc func manageSubscriptions(_ call: CAPPluginCall) {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
call.reject("Unable to get window scene")
|
|
294
|
-
return
|
|
295
|
-
}
|
|
296
|
-
// Open the App Store subscription management page
|
|
297
|
-
try await AppStore.showManageSubscriptions(in: windowScene)
|
|
298
|
-
call.resolve()
|
|
299
|
-
} catch {
|
|
300
|
-
print("manageSubscriptions error: \(error)")
|
|
301
|
-
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
|
|
302
222
|
}
|
|
223
|
+
try await AppStore.showManageSubscriptions(in: windowScene)
|
|
224
|
+
call.resolve()
|
|
225
|
+
} catch {
|
|
226
|
+
print("manageSubscriptions error: \(error)")
|
|
227
|
+
call.reject(error.localizedDescription)
|
|
303
228
|
}
|
|
304
|
-
} else {
|
|
305
|
-
print("Not implemented under iOS 15")
|
|
306
|
-
call.reject("Not implemented under iOS 15")
|
|
307
229
|
}
|
|
308
230
|
}
|
|
309
231
|
|
|
310
232
|
@objc func acknowledgePurchase(_ call: CAPPluginCall) {
|
|
311
|
-
|
|
312
|
-
print("acknowledgePurchase called on iOS")
|
|
233
|
+
print("acknowledgePurchase called on iOS")
|
|
313
234
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
// On iOS, purchaseToken is the transactionId (UInt64 as string)
|
|
320
|
-
guard let transactionId = UInt64(purchaseToken) else {
|
|
321
|
-
call.reject("Invalid purchaseToken format")
|
|
322
|
-
return
|
|
323
|
-
}
|
|
235
|
+
guard let purchaseToken = call.getString("purchaseToken") else {
|
|
236
|
+
call.reject("purchaseToken is required")
|
|
237
|
+
return
|
|
238
|
+
}
|
|
324
239
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
for await verificationResult in Transaction.all {
|
|
331
|
-
switch verificationResult {
|
|
332
|
-
case .verified(let transaction):
|
|
333
|
-
if transaction.id == transactionId {
|
|
334
|
-
foundTransaction = transaction
|
|
335
|
-
break
|
|
336
|
-
}
|
|
337
|
-
case .unverified:
|
|
338
|
-
continue
|
|
339
|
-
}
|
|
340
|
-
if foundTransaction != nil {
|
|
341
|
-
break
|
|
342
|
-
}
|
|
343
|
-
}
|
|
240
|
+
guard let transactionId = UInt64(purchaseToken) else {
|
|
241
|
+
call.reject("Invalid purchaseToken format")
|
|
242
|
+
return
|
|
243
|
+
}
|
|
344
244
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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
|
|
350
251
|
}
|
|
252
|
+
}
|
|
351
253
|
|
|
352
|
-
|
|
353
|
-
await transaction.finish()
|
|
354
|
-
|
|
254
|
+
guard let transaction = foundTransaction else {
|
|
355
255
|
await MainActor.run {
|
|
356
|
-
|
|
357
|
-
call.resolve()
|
|
256
|
+
call.reject("Transaction not found or already finished. Transaction ID: \(transactionId)")
|
|
358
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()
|
|
359
266
|
}
|
|
360
|
-
} else {
|
|
361
|
-
call.reject("Not implemented under iOS 15")
|
|
362
267
|
}
|
|
363
268
|
}
|
|
364
269
|
|
|
@@ -366,55 +271,16 @@ public class NativePurchasesPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
366
271
|
call.reject("consumePurchase is only available on Android")
|
|
367
272
|
}
|
|
368
273
|
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// MARK: - iOS 16+ App Transaction Methods
|
|
277
|
+
extension NativePurchasesPlugin {
|
|
369
278
|
@objc func getAppTransaction(_ call: CAPPluginCall) {
|
|
370
279
|
if #available(iOS 16.0, *) {
|
|
371
|
-
print("getAppTransaction called on iOS")
|
|
372
280
|
Task { @MainActor in
|
|
373
|
-
|
|
374
|
-
let verificationResult = try await AppTransaction.shared
|
|
375
|
-
switch verificationResult {
|
|
376
|
-
case .verified(let appTransaction):
|
|
377
|
-
var response: [String: Any] = [:]
|
|
378
|
-
|
|
379
|
-
// originalAppVersion is the CFBundleVersion (build number) at the time of original download
|
|
380
|
-
response["originalAppVersion"] = appTransaction.originalAppVersion
|
|
381
|
-
|
|
382
|
-
// Original purchase date
|
|
383
|
-
response["originalPurchaseDate"] = ISO8601DateFormatter().string(from: appTransaction.originalPurchaseDate)
|
|
384
|
-
|
|
385
|
-
// Bundle ID
|
|
386
|
-
response["bundleId"] = appTransaction.bundleID
|
|
387
|
-
|
|
388
|
-
// Current app version (build number)
|
|
389
|
-
response["appVersion"] = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? ""
|
|
390
|
-
|
|
391
|
-
// Environment
|
|
392
|
-
switch appTransaction.environment {
|
|
393
|
-
case .sandbox:
|
|
394
|
-
response["environment"] = "Sandbox"
|
|
395
|
-
case .production:
|
|
396
|
-
response["environment"] = "Production"
|
|
397
|
-
case .xcode:
|
|
398
|
-
response["environment"] = "Xcode"
|
|
399
|
-
default:
|
|
400
|
-
response["environment"] = "Production"
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
// JWS representation for server-side verification
|
|
404
|
-
response["jwsRepresentation"] = verificationResult.jwsRepresentation
|
|
405
|
-
|
|
406
|
-
call.resolve(["appTransaction": response])
|
|
407
|
-
|
|
408
|
-
case .unverified(_, let error):
|
|
409
|
-
call.reject("App transaction verification failed: \(error.localizedDescription)")
|
|
410
|
-
}
|
|
411
|
-
} catch {
|
|
412
|
-
print("getAppTransaction error: \(error)")
|
|
413
|
-
call.reject("Failed to get app transaction: \(error.localizedDescription)")
|
|
414
|
-
}
|
|
281
|
+
await self.handleGetAppTransaction(call)
|
|
415
282
|
}
|
|
416
283
|
} else {
|
|
417
|
-
print("getAppTransaction not implemented under iOS 16")
|
|
418
284
|
call.reject("App Transaction requires iOS 16.0 or later")
|
|
419
285
|
}
|
|
420
286
|
}
|
|
@@ -426,44 +292,78 @@ public class NativePurchasesPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
426
292
|
}
|
|
427
293
|
|
|
428
294
|
if #available(iOS 16.0, *) {
|
|
429
|
-
print("isEntitledToOldBusinessModel called with targetBuildNumber: \(targetBuildNumber)")
|
|
430
295
|
Task { @MainActor in
|
|
431
|
-
|
|
432
|
-
let verificationResult = try await AppTransaction.shared
|
|
433
|
-
switch verificationResult {
|
|
434
|
-
case .verified(let appTransaction):
|
|
435
|
-
let originalBuildNumber = appTransaction.originalAppVersion
|
|
436
|
-
|
|
437
|
-
// Compare build numbers (integers)
|
|
438
|
-
let isOlder = self.compareVersions(originalBuildNumber, targetBuildNumber) < 0
|
|
439
|
-
|
|
440
|
-
call.resolve([
|
|
441
|
-
"isOlderVersion": isOlder,
|
|
442
|
-
"originalAppVersion": originalBuildNumber
|
|
443
|
-
])
|
|
444
|
-
|
|
445
|
-
case .unverified(_, let error):
|
|
446
|
-
call.reject("App transaction verification failed: \(error.localizedDescription)")
|
|
447
|
-
}
|
|
448
|
-
} catch {
|
|
449
|
-
print("isEntitledToOldBusinessModel error: \(error)")
|
|
450
|
-
call.reject("Failed to get app transaction: \(error.localizedDescription)")
|
|
451
|
-
}
|
|
296
|
+
await self.handleIsEntitledToOldBusinessModel(call, targetBuildNumber: targetBuildNumber)
|
|
452
297
|
}
|
|
453
298
|
} else {
|
|
454
|
-
print("isEntitledToOldBusinessModel not implemented under iOS 16")
|
|
455
299
|
call.reject("App Transaction requires iOS 16.0 or later")
|
|
456
300
|
}
|
|
457
301
|
}
|
|
458
302
|
|
|
459
|
-
|
|
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
|
+
}
|
|
460
330
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
private func
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
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
|
+
}
|
|
467
356
|
}
|
|
357
|
+
}
|
|
468
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
|
+
}
|
|
469
369
|
}
|