@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.
- package/README.md +323 -48
- package/android/build.gradle +2 -2
- package/android/src/main/java/ee/forgr/nativepurchases/NativePurchasesPlugin.java +190 -74
- package/dist/docs.json +82 -4
- package/dist/esm/definitions.d.ts +120 -0
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +3 -0
- package/dist/esm/web.js +3 -0
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +3 -0
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +3 -0
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/NativePurchasesPlugin/NativePurchasesPlugin.swift +209 -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"
|
|
@@ -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.
|
|
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
|
-
|
|
37
|
-
if #available(iOS 15.0, *) {
|
|
38
|
-
startTransactionUpdatesListener()
|
|
39
|
-
}
|
|
33
|
+
startTransactionUpdatesListener()
|
|
40
34
|
}
|
|
41
35
|
|
|
42
36
|
deinit {
|
|
43
|
-
|
|
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
|
-
|
|
54
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
122
|
-
let result = try await product.purchase(options: purchaseOptions)
|
|
123
|
-
print("purchaseProduct result \(result)")
|
|
86
|
+
print("Auto-acknowledge enabled: \(autoAcknowledge)")
|
|
124
87
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
143
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
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
|
-
|
|
311
|
-
print("acknowledgePurchase called on iOS")
|
|
233
|
+
print("acknowledgePurchase called on iOS")
|
|
312
234
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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
|
-
|
|
352
|
-
await transaction.finish()
|
|
353
|
-
|
|
254
|
+
guard let transaction = foundTransaction else {
|
|
354
255
|
await MainActor.run {
|
|
355
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
457
|
-
|
|
458
|
-
private func
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
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
|
}
|