react-native-iap 10.0.1 → 10.0.5
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 +5 -5
- package/android/src/amazon/java/com/dooboolab/RNIap/RNIapAmazonListener.kt +4 -0
- package/android/src/amazon/java/com/dooboolab/RNIap/RNIapAmazonModule.kt +7 -1
- package/android/src/main/java/com/dooboolab/RNIap/DoobooUtils.kt +8 -0
- package/android/src/{play → main}/java/com/dooboolab/RNIap/PromiseUtlis.kt +4 -2
- package/android/src/play/java/com/dooboolab/RNIap/RNIapModule.kt +2 -0
- package/ios/RNIapIos.swift +875 -850
- package/lib/commonjs/purchaseError.js +1 -0
- package/lib/commonjs/purchaseError.js.map +1 -1
- package/lib/module/purchaseError.js +1 -0
- package/lib/module/purchaseError.js.map +1 -1
- package/lib/typescript/purchaseError.d.ts +2 -1
- package/package.json +1 -1
- package/src/purchaseError.ts +1 -0
package/ios/RNIapIos.swift
CHANGED
|
@@ -4,967 +4,992 @@ import StoreKit
|
|
|
4
4
|
typealias RNIapIosPromise = (RCTPromiseResolveBlock, RCTPromiseRejectBlock)
|
|
5
5
|
|
|
6
6
|
public func debugMessage(_ object: Any...) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
#if DEBUG
|
|
8
|
+
for item in object {
|
|
9
|
+
print("[react-native-iap] \(item)")
|
|
10
|
+
}
|
|
11
|
+
#endif
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
// Based on https://stackoverflow.com/a/40135192/570612
|
|
15
15
|
extension Date {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
var millisecondsSince1970: Int64 {
|
|
17
|
+
return Int64((self.timeIntervalSince1970 * 1000.0).rounded())
|
|
18
|
+
}
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
var millisecondsSince1970String: String {
|
|
21
|
+
return String((self.timeIntervalSince1970 * 1000.0).rounded())
|
|
22
|
+
}
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
init(milliseconds: Int64) {
|
|
25
|
+
self = Date(timeIntervalSince1970: TimeInterval(milliseconds) / 1000)
|
|
26
|
+
}
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
extension SKProductsRequest {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
var key: String {
|
|
31
|
+
return String(self.hashValue)
|
|
32
|
+
}
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
@objc(RNIapIos)
|
|
36
36
|
class RNIapIos: RCTEventEmitter, SKRequestDelegate, SKPaymentTransactionObserver, SKProductsRequestDelegate {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
func removeTransactionObserver() {
|
|
74
|
-
if hasTransactionObserver {
|
|
75
|
-
hasTransactionObserver = false
|
|
76
|
-
SKPaymentQueue.default().remove(self)
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
func flushUnheardEvents() {
|
|
81
|
-
paymentQueue(SKPaymentQueue.default(), updatedTransactions: SKPaymentQueue.default().transactions)
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
override func startObserving() {
|
|
85
|
-
hasListeners = true
|
|
86
|
-
flushUnheardEvents()
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
override func stopObserving() {
|
|
90
|
-
hasListeners = false
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
override func addListener(_ eventName: String?) {
|
|
94
|
-
super.addListener(eventName)
|
|
95
|
-
|
|
96
|
-
if (eventName == "iap-promoted-product") && promotedPayment != nil {
|
|
97
|
-
sendEvent(withName: "iap-promoted-product", body: promotedPayment?.productIdentifier)
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
func addPromise(forKey key: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
102
|
-
var promises: [RNIapIosPromise]? = promisesByKey[key]
|
|
103
|
-
|
|
104
|
-
if promises == nil {
|
|
105
|
-
promises = []
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
promises?.append((resolve, reject))
|
|
109
|
-
promisesByKey[key] = promises
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
func resolvePromises(forKey key: String?, value: Any?) {
|
|
113
|
-
let promises: [RNIapIosPromise]? = promisesByKey[key ?? ""]
|
|
114
|
-
|
|
115
|
-
if let promises = promises {
|
|
116
|
-
for tuple in promises {
|
|
117
|
-
let resolveBlck = tuple.0
|
|
118
|
-
resolveBlck(value)
|
|
119
|
-
}
|
|
120
|
-
promisesByKey[key ?? ""] = nil
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
func rejectPromises(forKey key: String, code: String?, message: String?, error: Error?) {
|
|
125
|
-
let promises = promisesByKey[key]
|
|
126
|
-
|
|
127
|
-
if let promises = promises {
|
|
128
|
-
for tuple in promises {
|
|
129
|
-
let reject = tuple.1
|
|
130
|
-
reject(code, message, error)
|
|
131
|
-
}
|
|
132
|
-
promisesByKey[key] = nil
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
func paymentQueue(_ queue: SKPaymentQueue, shouldAddStorePayment payment: SKPayment, for product: SKProduct) -> Bool {
|
|
137
|
-
promotedProduct = product
|
|
138
|
-
promotedPayment = payment
|
|
139
|
-
|
|
140
|
-
if hasListeners {
|
|
141
|
-
sendEvent(withName: "iap-promoted-product", body: product.productIdentifier)
|
|
142
|
-
}
|
|
143
|
-
return false
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
override func supportedEvents() -> [String]? {
|
|
147
|
-
return ["iap-promoted-product", "purchase-updated", "purchase-error"]
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
@objc public func initConnection(
|
|
151
|
-
_ resolve: @escaping RCTPromiseResolveBlock = { _ in },
|
|
152
|
-
reject: @escaping RCTPromiseRejectBlock = { _, _, _ in }
|
|
153
|
-
) {
|
|
154
|
-
addTransactionObserver()
|
|
155
|
-
let canMakePayments = SKPaymentQueue.canMakePayments()
|
|
156
|
-
resolve(NSNumber(value: canMakePayments))
|
|
157
|
-
}
|
|
158
|
-
@objc public func endConnection(
|
|
159
|
-
_ resolve: @escaping RCTPromiseResolveBlock = { _ in },
|
|
160
|
-
reject: @escaping RCTPromiseRejectBlock = { _, _, _ in }
|
|
161
|
-
) {
|
|
162
|
-
removeTransactionObserver()
|
|
163
|
-
resolve(nil)
|
|
164
|
-
}
|
|
165
|
-
@objc public func getItems(
|
|
166
|
-
_ skus: [String],
|
|
167
|
-
resolve: @escaping RCTPromiseResolveBlock = { _ in },
|
|
168
|
-
reject: @escaping RCTPromiseRejectBlock = { _, _, _ in }
|
|
169
|
-
) {
|
|
170
|
-
let productIdentifiers = Set<AnyHashable>(skus)
|
|
171
|
-
if let productIdentifiers = productIdentifiers as? Set<String> {
|
|
172
|
-
productsRequest = SKProductsRequest(productIdentifiers: productIdentifiers)
|
|
173
|
-
if let productsRequest = productsRequest {
|
|
174
|
-
productsRequest.delegate = self
|
|
175
|
-
let key: String = productsRequest.key
|
|
176
|
-
addPromise(forKey: key, resolve: resolve, reject: reject)
|
|
177
|
-
productsRequest.start()
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
@objc public func getAvailableItems(
|
|
182
|
-
_ resolve: @escaping RCTPromiseResolveBlock = { _ in },
|
|
183
|
-
reject: @escaping RCTPromiseRejectBlock = { _, _, _ in }
|
|
184
|
-
) {
|
|
185
|
-
addPromise(forKey: "availableItems", resolve: resolve, reject: reject)
|
|
186
|
-
SKPaymentQueue.default().restoreCompletedTransactions()
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
@objc public func buyProduct(
|
|
190
|
-
_ sku: String,
|
|
191
|
-
andDangerouslyFinishTransactionAutomatically: Bool,
|
|
192
|
-
applicationUsername: String?,
|
|
193
|
-
resolve: @escaping RCTPromiseResolveBlock = { _ in },
|
|
194
|
-
reject: @escaping RCTPromiseRejectBlock = { _, _, _ in }
|
|
195
|
-
) {
|
|
196
|
-
pendingTransactionWithAutoFinish = andDangerouslyFinishTransactionAutomatically
|
|
197
|
-
var product: SKProduct?
|
|
198
|
-
let lockQueue = DispatchQueue(label: "validProducts")
|
|
199
|
-
lockQueue.sync {
|
|
200
|
-
for p in validProducts {
|
|
201
|
-
if sku == p.productIdentifier {
|
|
202
|
-
product = p
|
|
203
|
-
break
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
if let prod = product {
|
|
208
|
-
addPromise(forKey: prod.productIdentifier, resolve: resolve, reject: reject)
|
|
209
|
-
|
|
210
|
-
let payment = SKMutablePayment(product: prod)
|
|
211
|
-
|
|
212
|
-
if let applicationUsername = applicationUsername {
|
|
213
|
-
payment.applicationUsername = applicationUsername
|
|
214
|
-
}
|
|
215
|
-
SKPaymentQueue.default().add(payment)
|
|
216
|
-
} else {
|
|
217
|
-
if hasListeners {
|
|
218
|
-
let err = [
|
|
219
|
-
"debugMessage": "Invalid product ID.",
|
|
220
|
-
"code": "E_DEVELOPER_ERROR",
|
|
221
|
-
"message": "Invalid product ID.",
|
|
222
|
-
"productId": sku
|
|
223
|
-
]
|
|
37
|
+
private var promisesByKey: [String: [RNIapIosPromise]]
|
|
38
|
+
private var myQueue: DispatchQueue
|
|
39
|
+
private var hasListeners = false
|
|
40
|
+
private var pendingTransactionWithAutoFinish = false
|
|
41
|
+
private var receiptBlock: ((Data?, Error?) -> Void)? // Block to handle request the receipt async from delegate
|
|
42
|
+
private var validProducts: [SKProduct]
|
|
43
|
+
private var promotedPayment: SKPayment?
|
|
44
|
+
private var promotedProduct: SKProduct?
|
|
45
|
+
private var productsRequest: SKProductsRequest?
|
|
46
|
+
private var countPendingTransaction: Int = 0
|
|
47
|
+
private var hasTransactionObserver = false
|
|
48
|
+
|
|
49
|
+
override init() {
|
|
50
|
+
promisesByKey = [String: [RNIapIosPromise]]()
|
|
51
|
+
pendingTransactionWithAutoFinish = false
|
|
52
|
+
myQueue = DispatchQueue(label: "reject")
|
|
53
|
+
validProducts = [SKProduct]()
|
|
54
|
+
super.init()
|
|
55
|
+
addTransactionObserver()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
deinit {
|
|
59
|
+
removeTransactionObserver()
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
override class func requiresMainQueueSetup() -> Bool {
|
|
63
|
+
return true
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
func addTransactionObserver() {
|
|
67
|
+
if !hasTransactionObserver {
|
|
68
|
+
hasTransactionObserver = true
|
|
69
|
+
SKPaymentQueue.default().add(self)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
224
72
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
@objc public func buyProductWithOffer(
|
|
233
|
-
_ sku: String,
|
|
234
|
-
forUser usernameHash: String,
|
|
235
|
-
withOffer discountOffer: [String: String],
|
|
236
|
-
resolve: @escaping RCTPromiseResolveBlock = { _ in },
|
|
237
|
-
reject: @escaping RCTPromiseRejectBlock = { _, _, _ in }
|
|
238
|
-
) {
|
|
239
|
-
var product: SKProduct?
|
|
240
|
-
let lockQueue = DispatchQueue(label: "validProducts")
|
|
241
|
-
lockQueue.sync {
|
|
242
|
-
for p in validProducts {
|
|
243
|
-
if sku == p.productIdentifier {
|
|
244
|
-
product = p
|
|
245
|
-
break
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
if let prod = product {
|
|
251
|
-
addPromise(forKey: prod.productIdentifier, resolve: resolve, reject: reject)
|
|
252
|
-
|
|
253
|
-
let payment = SKMutablePayment(product: prod)
|
|
254
|
-
|
|
255
|
-
if #available(iOS 12.2, tvOS 12.2, *) {
|
|
256
|
-
let discount = SKPaymentDiscount(
|
|
257
|
-
identifier: discountOffer["identifier"]!,
|
|
258
|
-
keyIdentifier: discountOffer["keyIdentifier"]!,
|
|
259
|
-
nonce: UUID(uuidString: discountOffer["nonce"]!)!,
|
|
260
|
-
signature: discountOffer["signature"]!,
|
|
261
|
-
timestamp: NSNumber(value: Int(discountOffer["timestamp"]!)!))
|
|
262
|
-
payment.paymentDiscount = discount
|
|
263
|
-
}
|
|
264
|
-
payment.applicationUsername = usernameHash
|
|
265
|
-
SKPaymentQueue.default().add(payment)
|
|
266
|
-
} else {
|
|
267
|
-
if hasListeners {
|
|
268
|
-
let err = [
|
|
269
|
-
"debugMessage": "Invalid product ID.",
|
|
270
|
-
"message": "Invalid product ID.",
|
|
271
|
-
"code": "E_DEVELOPER_ERROR",
|
|
272
|
-
"productId": sku
|
|
273
|
-
]
|
|
274
|
-
sendEvent(withName: "purchase-error", body: err)
|
|
275
|
-
}
|
|
276
|
-
reject("E_DEVELOPER_ERROR", "Invalid product ID.", nil)
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
@objc public func buyProductWithQuantityIOS(
|
|
281
|
-
_ sku: String,
|
|
282
|
-
quantity: Int,
|
|
283
|
-
resolve: @escaping RCTPromiseResolveBlock = { _ in },
|
|
284
|
-
reject: @escaping RCTPromiseRejectBlock = { _, _, _ in }
|
|
285
|
-
) {
|
|
286
|
-
debugMessage("buyProductWithQuantityIOS")
|
|
287
|
-
var product: SKProduct?
|
|
288
|
-
let lockQueue = DispatchQueue(label: "validProducts")
|
|
289
|
-
lockQueue.sync {
|
|
290
|
-
for p in validProducts {
|
|
291
|
-
if sku == p.productIdentifier {
|
|
292
|
-
product = p
|
|
293
|
-
break
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
if let prod = product {
|
|
298
|
-
let payment = SKMutablePayment(product: prod)
|
|
299
|
-
payment.quantity = quantity
|
|
300
|
-
SKPaymentQueue.default().add(payment)
|
|
301
|
-
} else {
|
|
302
|
-
if hasListeners {
|
|
303
|
-
let err = [
|
|
304
|
-
"debugMessage": "Invalid product ID.",
|
|
305
|
-
"message": "Invalid product ID.",
|
|
306
|
-
"code": "E_DEVELOPER_ERROR",
|
|
307
|
-
"productId": sku
|
|
308
|
-
]
|
|
309
|
-
sendEvent(withName: "purchase-error", body: err)
|
|
310
|
-
}
|
|
311
|
-
reject("E_DEVELOPER_ERROR", "Invalid product ID.", nil)
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
@objc public func clearTransaction(
|
|
316
|
-
_ resolve: @escaping RCTPromiseResolveBlock = { _ in },
|
|
317
|
-
reject: @escaping RCTPromiseRejectBlock = { _, _, _ in }
|
|
318
|
-
) {
|
|
319
|
-
let pendingTrans = SKPaymentQueue.default().transactions
|
|
320
|
-
countPendingTransaction = pendingTrans.count
|
|
321
|
-
|
|
322
|
-
debugMessage("clear remaining Transactions (\(countPendingTransaction)). Call this before make a new transaction")
|
|
323
|
-
|
|
324
|
-
if countPendingTransaction > 0 {
|
|
325
|
-
addPromise(forKey: "cleaningTransactions", resolve: resolve, reject: reject)
|
|
326
|
-
for transaction in pendingTrans {
|
|
327
|
-
SKPaymentQueue.default().finishTransaction(transaction)
|
|
328
|
-
}
|
|
329
|
-
} else {
|
|
330
|
-
resolve(nil)
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
@objc public func clearProducts(
|
|
335
|
-
_ resolve: @escaping RCTPromiseResolveBlock = { _ in },
|
|
336
|
-
reject: @escaping RCTPromiseRejectBlock = { _, _, _ in }
|
|
337
|
-
) {
|
|
338
|
-
debugMessage("clear valid products")
|
|
339
|
-
|
|
340
|
-
let lockQueue = DispatchQueue(label: "validProducts")
|
|
341
|
-
|
|
342
|
-
lockQueue.sync {
|
|
343
|
-
validProducts.removeAll()
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
resolve(nil)
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
@objc public func promotedProduct(
|
|
350
|
-
_ resolve: @escaping RCTPromiseResolveBlock = { _ in },
|
|
351
|
-
reject: @escaping RCTPromiseRejectBlock = { _, _, _ in }
|
|
352
|
-
) {
|
|
353
|
-
debugMessage("get promoted product")
|
|
354
|
-
resolve((promotedProduct != nil) ? getProductObject(promotedProduct!) : nil)
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
@objc public func buyPromotedProduct(
|
|
358
|
-
_ resolve: @escaping RCTPromiseResolveBlock = { _ in },
|
|
359
|
-
reject: @escaping RCTPromiseRejectBlock = { _, _, _ in }
|
|
360
|
-
) {
|
|
361
|
-
if let promoPayment = promotedPayment {
|
|
362
|
-
debugMessage("buy promoted product")
|
|
363
|
-
SKPaymentQueue.default().add(promoPayment)
|
|
364
|
-
} else {
|
|
365
|
-
reject("E_DEVELOPER_ERROR", "Invalid product ID.", nil)
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
@objc public func requestReceipt(
|
|
370
|
-
_ refresh: Bool,
|
|
371
|
-
resolve: @escaping RCTPromiseResolveBlock = { _ in },
|
|
372
|
-
reject: @escaping RCTPromiseRejectBlock = { _, _, _ in }
|
|
373
|
-
) {
|
|
374
|
-
requestReceiptData(withBlock: refresh) { [self] receiptData, error in
|
|
375
|
-
if error == nil {
|
|
376
|
-
resolve(receiptData?.base64EncodedString(options: []))
|
|
377
|
-
} else {
|
|
378
|
-
reject(standardErrorCode(9), "Invalid receipt", nil)
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
@objc public func finishTransaction(
|
|
384
|
-
_ transactionIdentifier: String,
|
|
385
|
-
resolve: @escaping RCTPromiseResolveBlock = { _ in },
|
|
386
|
-
reject: @escaping RCTPromiseRejectBlock = { _, _, _ in }
|
|
387
|
-
) {
|
|
388
|
-
finishTransaction(withIdentifier: transactionIdentifier)
|
|
389
|
-
resolve(nil)
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
@objc public func getPendingTransactions (
|
|
393
|
-
_ resolve: @escaping RCTPromiseResolveBlock = { _ in },
|
|
394
|
-
reject: @escaping RCTPromiseRejectBlock = { _, _, _ in }
|
|
395
|
-
) {
|
|
396
|
-
requestReceiptData(withBlock: false) { receiptData, _ in
|
|
397
|
-
var output: [AnyHashable] = []
|
|
398
|
-
|
|
399
|
-
if let receipt = receiptData {
|
|
400
|
-
let transactions = SKPaymentQueue.default().transactions
|
|
401
|
-
|
|
402
|
-
for item in transactions {
|
|
403
|
-
let timestamp = item.transactionDate?.millisecondsSince1970 == nil ? nil : String(item.transactionDate!.millisecondsSince1970)
|
|
404
|
-
let purchase = [
|
|
405
|
-
"transactionDate": timestamp,
|
|
406
|
-
"transactionId": item.transactionIdentifier,
|
|
407
|
-
"productId": item.payment.productIdentifier,
|
|
408
|
-
"quantity": "\(item.payment.quantity)",
|
|
409
|
-
"transactionReceipt": receipt.base64EncodedString(options: [])
|
|
410
|
-
]
|
|
411
|
-
|
|
412
|
-
output.append(purchase)
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
resolve(output)
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
@objc public func presentCodeRedemptionSheet(
|
|
421
|
-
_ resolve: @escaping RCTPromiseResolveBlock = { _ in },
|
|
422
|
-
reject: @escaping RCTPromiseRejectBlock = { _, _, _ in }
|
|
423
|
-
) {
|
|
424
|
-
#if !os(tvOS)
|
|
425
|
-
if #available(iOS 14.0, tvOS 14.0, *) {
|
|
426
|
-
SKPaymentQueue.default().presentCodeRedemptionSheet()
|
|
427
|
-
resolve(nil)
|
|
428
|
-
} else {
|
|
429
|
-
reject(standardErrorCode(2), "This method only available above iOS 14", nil)
|
|
430
|
-
}
|
|
431
|
-
#else
|
|
432
|
-
reject(standardErrorCode(2), "This method is not available on tvOS", nil)
|
|
433
|
-
#endif
|
|
434
|
-
}
|
|
73
|
+
func removeTransactionObserver() {
|
|
74
|
+
if hasTransactionObserver {
|
|
75
|
+
hasTransactionObserver = false
|
|
76
|
+
SKPaymentQueue.default().remove(self)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
435
79
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
for prod in response.products {
|
|
439
|
-
add(prod)
|
|
80
|
+
func flushUnheardEvents() {
|
|
81
|
+
paymentQueue(SKPaymentQueue.default(), updatedTransactions: SKPaymentQueue.default().transactions)
|
|
440
82
|
}
|
|
441
83
|
|
|
442
|
-
|
|
443
|
-
|
|
84
|
+
override func startObserving() {
|
|
85
|
+
hasListeners = true
|
|
86
|
+
flushUnheardEvents()
|
|
87
|
+
}
|
|
444
88
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
items.append(getProductObject(product))
|
|
448
|
-
}
|
|
89
|
+
override func stopObserving() {
|
|
90
|
+
hasListeners = false
|
|
449
91
|
}
|
|
450
92
|
|
|
451
|
-
|
|
452
|
-
|
|
93
|
+
override func addListener(_ eventName: String?) {
|
|
94
|
+
super.addListener(eventName)
|
|
453
95
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
96
|
+
if (eventName == "iap-promoted-product") && promotedPayment != nil {
|
|
97
|
+
sendEvent(withName: "iap-promoted-product", body: promotedPayment?.productIdentifier)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
458
100
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
var delTar = -1
|
|
101
|
+
func addPromise(forKey key: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
102
|
+
var promises: [RNIapIosPromise]? = promisesByKey[key]
|
|
462
103
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
if cur.productIdentifier == aProd.productIdentifier {
|
|
466
|
-
delTar = k
|
|
104
|
+
if promises == nil {
|
|
105
|
+
promises = []
|
|
467
106
|
}
|
|
468
|
-
}
|
|
469
107
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
108
|
+
promises?.append((resolve, reject))
|
|
109
|
+
promisesByKey[key] = promises
|
|
110
|
+
}
|
|
473
111
|
|
|
474
|
-
|
|
112
|
+
func resolvePromises(forKey key: String?, value: Any?) {
|
|
113
|
+
let promises: [RNIapIosPromise]? = promisesByKey[key ?? ""]
|
|
114
|
+
|
|
115
|
+
if let promises = promises {
|
|
116
|
+
for tuple in promises {
|
|
117
|
+
let resolveBlck = tuple.0
|
|
118
|
+
resolveBlck(value)
|
|
119
|
+
}
|
|
120
|
+
promisesByKey[key ?? ""] = nil
|
|
121
|
+
}
|
|
475
122
|
}
|
|
476
|
-
}
|
|
477
123
|
|
|
478
|
-
|
|
479
|
-
|
|
124
|
+
func rejectPromises(forKey key: String, code: String?, message: String?, error: Error?) {
|
|
125
|
+
let promises = promisesByKey[key]
|
|
480
126
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
myQueue.sync(execute: { [self] in
|
|
490
|
-
rejectPromises(
|
|
491
|
-
forKey: key,
|
|
492
|
-
code: standardErrorCode(nsError.code),
|
|
493
|
-
message: error.localizedDescription,
|
|
494
|
-
error: error)}
|
|
495
|
-
)
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
|
|
502
|
-
for transaction in transactions {
|
|
503
|
-
switch transaction.transactionState {
|
|
504
|
-
case .purchasing:
|
|
505
|
-
debugMessage("Purchase Started")
|
|
506
|
-
break
|
|
507
|
-
|
|
508
|
-
case .purchased:
|
|
509
|
-
debugMessage("Purchase Successful")
|
|
510
|
-
purchaseProcess(transaction)
|
|
511
|
-
break
|
|
512
|
-
|
|
513
|
-
case .restored:
|
|
514
|
-
debugMessage("Restored")
|
|
515
|
-
SKPaymentQueue.default().finishTransaction(transaction)
|
|
516
|
-
break
|
|
517
|
-
|
|
518
|
-
case .deferred:
|
|
519
|
-
debugMessage("Deferred (awaiting approval via parental controls, etc.)")
|
|
127
|
+
if let promises = promises {
|
|
128
|
+
for tuple in promises {
|
|
129
|
+
let reject = tuple.1
|
|
130
|
+
reject(code, message, error)
|
|
131
|
+
}
|
|
132
|
+
promisesByKey[key] = nil
|
|
133
|
+
}
|
|
134
|
+
}
|
|
520
135
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
let
|
|
524
|
-
|
|
525
|
-
"code": "E_DEFERRED_PAYMENT",
|
|
526
|
-
"message": "The payment was deferred (awaiting approval via parental controls for instance)",
|
|
527
|
-
"productId": transaction.payment.productIdentifier,
|
|
528
|
-
"quantity": "\(transaction.payment.quantity)"
|
|
529
|
-
]
|
|
530
|
-
|
|
531
|
-
sendEvent(withName: "purchase-error", body: err)
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
rejectPromises(
|
|
535
|
-
forKey: transaction.payment.productIdentifier,
|
|
536
|
-
code: "E_DEFERRED_PAYMENT",
|
|
537
|
-
message: "The payment was deferred (awaiting approval via parental controls for instance)",
|
|
538
|
-
error: nil)
|
|
136
|
+
func rejectAllPendingPromises() {
|
|
137
|
+
promisesByKey.values.reduce([], +).forEach({tuple in
|
|
138
|
+
let reject = tuple.1
|
|
139
|
+
reject("E_CONNECTION_CLOSED", "Connection has been closed", nil)
|
|
539
140
|
})
|
|
141
|
+
promisesByKey.removeAll()
|
|
142
|
+
}
|
|
540
143
|
|
|
541
|
-
|
|
542
|
-
|
|
144
|
+
func paymentQueue(_ queue: SKPaymentQueue, shouldAddStorePayment payment: SKPayment, for product: SKProduct) -> Bool {
|
|
145
|
+
promotedProduct = product
|
|
146
|
+
promotedPayment = payment
|
|
543
147
|
|
|
544
|
-
|
|
148
|
+
if hasListeners {
|
|
149
|
+
sendEvent(withName: "iap-promoted-product", body: product.productIdentifier)
|
|
150
|
+
}
|
|
151
|
+
return false
|
|
152
|
+
}
|
|
545
153
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
if hasListeners {
|
|
550
|
-
let code = nsError?.code
|
|
551
|
-
let responseCode = String(code ?? 0)
|
|
552
|
-
let err = [
|
|
553
|
-
"responseCode": responseCode,
|
|
554
|
-
"debugMessage": transaction.error?.localizedDescription,
|
|
555
|
-
"code": standardErrorCode(code),
|
|
556
|
-
"message": transaction.error?.localizedDescription,
|
|
557
|
-
"productId": transaction.payment.productIdentifier
|
|
558
|
-
]
|
|
559
|
-
|
|
560
|
-
sendEvent(withName: "purchase-error", body: err)
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
rejectPromises(
|
|
564
|
-
forKey: transaction.payment.productIdentifier,
|
|
565
|
-
code: standardErrorCode(nsError?.code),
|
|
566
|
-
message: nsError?.localizedDescription,
|
|
567
|
-
error: nsError)
|
|
568
|
-
})
|
|
154
|
+
override func supportedEvents() -> [String]? {
|
|
155
|
+
return ["iap-promoted-product", "purchase-updated", "purchase-error"]
|
|
156
|
+
}
|
|
569
157
|
|
|
570
|
-
|
|
571
|
-
|
|
158
|
+
@objc public func initConnection(
|
|
159
|
+
_ resolve: @escaping RCTPromiseResolveBlock = { _ in },
|
|
160
|
+
reject: @escaping RCTPromiseRejectBlock = { _, _, _ in }
|
|
161
|
+
) {
|
|
162
|
+
addTransactionObserver()
|
|
163
|
+
let canMakePayments = SKPaymentQueue.canMakePayments()
|
|
164
|
+
resolve(NSNumber(value: canMakePayments))
|
|
165
|
+
}
|
|
166
|
+
@objc public func endConnection(
|
|
167
|
+
_ resolve: @escaping RCTPromiseResolveBlock = { _ in },
|
|
168
|
+
reject: @escaping RCTPromiseRejectBlock = { _, _, _ in }
|
|
169
|
+
) {
|
|
170
|
+
removeTransactionObserver()
|
|
171
|
+
stopObserving()
|
|
172
|
+
rejectAllPendingPromises()
|
|
173
|
+
receiptBlock = nil
|
|
174
|
+
validProducts.removeAll()
|
|
175
|
+
promotedPayment = nil
|
|
176
|
+
promotedProduct = nil
|
|
177
|
+
productsRequest = nil
|
|
178
|
+
countPendingTransaction = 0
|
|
179
|
+
resolve(nil)
|
|
180
|
+
}
|
|
181
|
+
@objc public func getItems(
|
|
182
|
+
_ skus: [String],
|
|
183
|
+
resolve: @escaping RCTPromiseResolveBlock = { _ in },
|
|
184
|
+
reject: @escaping RCTPromiseRejectBlock = { _, _, _ in }
|
|
185
|
+
) {
|
|
186
|
+
let productIdentifiers = Set<AnyHashable>(skus)
|
|
187
|
+
if let productIdentifiers = productIdentifiers as? Set<String> {
|
|
188
|
+
productsRequest = SKProductsRequest(productIdentifiers: productIdentifiers)
|
|
189
|
+
if let productsRequest = productsRequest {
|
|
190
|
+
productsRequest.delegate = self
|
|
191
|
+
let key: String = productsRequest.key
|
|
192
|
+
addPromise(forKey: key, resolve: resolve, reject: reject)
|
|
193
|
+
productsRequest.start()
|
|
194
|
+
}
|
|
195
|
+
}
|
|
572
196
|
}
|
|
573
|
-
|
|
197
|
+
@objc public func getAvailableItems(
|
|
198
|
+
_ resolve: @escaping RCTPromiseResolveBlock = { _ in },
|
|
199
|
+
reject: @escaping RCTPromiseRejectBlock = { _, _, _ in }
|
|
200
|
+
) {
|
|
201
|
+
addPromise(forKey: "availableItems", resolve: resolve, reject: reject)
|
|
202
|
+
SKPaymentQueue.default().restoreCompletedTransactions()
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
@objc public func buyProduct(
|
|
206
|
+
_ sku: String,
|
|
207
|
+
andDangerouslyFinishTransactionAutomatically: Bool,
|
|
208
|
+
applicationUsername: String?,
|
|
209
|
+
resolve: @escaping RCTPromiseResolveBlock = { _ in },
|
|
210
|
+
reject: @escaping RCTPromiseRejectBlock = { _, _, _ in }
|
|
211
|
+
) {
|
|
212
|
+
pendingTransactionWithAutoFinish = andDangerouslyFinishTransactionAutomatically
|
|
213
|
+
var product: SKProduct?
|
|
214
|
+
let lockQueue = DispatchQueue(label: "validProducts")
|
|
215
|
+
lockQueue.sync {
|
|
216
|
+
for p in validProducts {
|
|
217
|
+
if sku == p.productIdentifier {
|
|
218
|
+
product = p
|
|
219
|
+
break
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if let prod = product {
|
|
224
|
+
addPromise(forKey: prod.productIdentifier, resolve: resolve, reject: reject)
|
|
574
225
|
|
|
575
|
-
|
|
576
|
-
let queue = SKPaymentQueue.default()
|
|
226
|
+
let payment = SKMutablePayment(product: prod)
|
|
577
227
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
228
|
+
if let applicationUsername = applicationUsername {
|
|
229
|
+
payment.applicationUsername = applicationUsername
|
|
230
|
+
}
|
|
231
|
+
SKPaymentQueue.default().add(payment)
|
|
232
|
+
} else {
|
|
233
|
+
if hasListeners {
|
|
234
|
+
let err = [
|
|
235
|
+
"debugMessage": "Invalid product ID.",
|
|
236
|
+
"code": "E_DEVELOPER_ERROR",
|
|
237
|
+
"message": "Invalid product ID.",
|
|
238
|
+
"productId": sku
|
|
239
|
+
]
|
|
240
|
+
|
|
241
|
+
sendEvent(withName: "purchase-error", body: err)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
reject("E_DEVELOPER_ERROR", "Invalid product ID.", nil)
|
|
245
|
+
}
|
|
582
246
|
}
|
|
583
|
-
}
|
|
584
247
|
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
248
|
+
@objc public func buyProductWithOffer(
|
|
249
|
+
_ sku: String,
|
|
250
|
+
forUser usernameHash: String,
|
|
251
|
+
withOffer discountOffer: [String: String],
|
|
252
|
+
resolve: @escaping RCTPromiseResolveBlock = { _ in },
|
|
253
|
+
reject: @escaping RCTPromiseRejectBlock = { _, _, _ in }
|
|
254
|
+
) {
|
|
255
|
+
var product: SKProduct?
|
|
256
|
+
let lockQueue = DispatchQueue(label: "validProducts")
|
|
257
|
+
lockQueue.sync {
|
|
258
|
+
for p in validProducts {
|
|
259
|
+
if sku == p.productIdentifier {
|
|
260
|
+
product = p
|
|
261
|
+
break
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
595
265
|
|
|
596
|
-
|
|
266
|
+
if let prod = product {
|
|
267
|
+
addPromise(forKey: prod.productIdentifier, resolve: resolve, reject: reject)
|
|
268
|
+
|
|
269
|
+
let payment = SKMutablePayment(product: prod)
|
|
270
|
+
|
|
271
|
+
if #available(iOS 12.2, tvOS 12.2, *) {
|
|
272
|
+
let discount = SKPaymentDiscount(
|
|
273
|
+
identifier: discountOffer["identifier"]!,
|
|
274
|
+
keyIdentifier: discountOffer["keyIdentifier"]!,
|
|
275
|
+
nonce: UUID(uuidString: discountOffer["nonce"]!)!,
|
|
276
|
+
signature: discountOffer["signature"]!,
|
|
277
|
+
timestamp: NSNumber(value: Int(discountOffer["timestamp"]!)!))
|
|
278
|
+
payment.paymentDiscount = discount
|
|
279
|
+
}
|
|
280
|
+
payment.applicationUsername = usernameHash
|
|
281
|
+
SKPaymentQueue.default().add(payment)
|
|
282
|
+
} else {
|
|
283
|
+
if hasListeners {
|
|
284
|
+
let err = [
|
|
285
|
+
"debugMessage": "Invalid product ID.",
|
|
286
|
+
"message": "Invalid product ID.",
|
|
287
|
+
"code": "E_DEVELOPER_ERROR",
|
|
288
|
+
"productId": sku
|
|
289
|
+
]
|
|
290
|
+
sendEvent(withName: "purchase-error", body: err)
|
|
291
|
+
}
|
|
292
|
+
reject("E_DEVELOPER_ERROR", "Invalid product ID.", nil)
|
|
597
293
|
}
|
|
598
|
-
}
|
|
599
294
|
}
|
|
600
295
|
|
|
601
|
-
|
|
602
|
-
|
|
296
|
+
@objc public func buyProductWithQuantityIOS(
|
|
297
|
+
_ sku: String,
|
|
298
|
+
quantity: Int,
|
|
299
|
+
resolve: @escaping RCTPromiseResolveBlock = { _ in },
|
|
300
|
+
reject: @escaping RCTPromiseRejectBlock = { _, _, _ in }
|
|
301
|
+
) {
|
|
302
|
+
debugMessage("buyProductWithQuantityIOS")
|
|
303
|
+
var product: SKProduct?
|
|
304
|
+
let lockQueue = DispatchQueue(label: "validProducts")
|
|
305
|
+
lockQueue.sync {
|
|
306
|
+
for p in validProducts {
|
|
307
|
+
if sku == p.productIdentifier {
|
|
308
|
+
product = p
|
|
309
|
+
break
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
if let prod = product {
|
|
314
|
+
let payment = SKMutablePayment(product: prod)
|
|
315
|
+
payment.quantity = quantity
|
|
316
|
+
SKPaymentQueue.default().add(payment)
|
|
317
|
+
} else {
|
|
318
|
+
if hasListeners {
|
|
319
|
+
let err = [
|
|
320
|
+
"debugMessage": "Invalid product ID.",
|
|
321
|
+
"message": "Invalid product ID.",
|
|
322
|
+
"code": "E_DEVELOPER_ERROR",
|
|
323
|
+
"productId": sku
|
|
324
|
+
]
|
|
325
|
+
sendEvent(withName: "purchase-error", body: err)
|
|
326
|
+
}
|
|
327
|
+
reject("E_DEVELOPER_ERROR", "Invalid product ID.", nil)
|
|
328
|
+
}
|
|
329
|
+
}
|
|
603
330
|
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
error: error)
|
|
611
|
-
})
|
|
331
|
+
@objc public func clearTransaction(
|
|
332
|
+
_ resolve: @escaping RCTPromiseResolveBlock = { _ in },
|
|
333
|
+
reject: @escaping RCTPromiseRejectBlock = { _, _, _ in }
|
|
334
|
+
) {
|
|
335
|
+
let pendingTrans = SKPaymentQueue.default().transactions
|
|
336
|
+
countPendingTransaction = pendingTrans.count
|
|
612
337
|
|
|
613
|
-
|
|
614
|
-
}
|
|
338
|
+
debugMessage("clear remaining Transactions (\(countPendingTransaction)). Call this before make a new transaction")
|
|
615
339
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
340
|
+
if countPendingTransaction > 0 {
|
|
341
|
+
addPromise(forKey: "cleaningTransactions", resolve: resolve, reject: reject)
|
|
342
|
+
for transaction in pendingTrans {
|
|
343
|
+
SKPaymentQueue.default().finishTransaction(transaction)
|
|
344
|
+
}
|
|
345
|
+
} else {
|
|
346
|
+
resolve(nil)
|
|
347
|
+
}
|
|
620
348
|
}
|
|
621
349
|
|
|
622
|
-
|
|
623
|
-
|
|
350
|
+
@objc public func clearProducts(
|
|
351
|
+
_ resolve: @escaping RCTPromiseResolveBlock = { _ in },
|
|
352
|
+
reject: @escaping RCTPromiseRejectBlock = { _, _, _ in }
|
|
353
|
+
) {
|
|
354
|
+
debugMessage("clear valid products")
|
|
624
355
|
|
|
625
|
-
|
|
626
|
-
if hasListeners {
|
|
627
|
-
sendEvent(withName: "purchase-updated", body: purchase)
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
}
|
|
356
|
+
let lockQueue = DispatchQueue(label: "validProducts")
|
|
631
357
|
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
"E_SERVICE_ERROR",
|
|
636
|
-
"E_USER_CANCELLED",
|
|
637
|
-
"E_USER_ERROR",
|
|
638
|
-
"E_USER_ERROR",
|
|
639
|
-
"E_ITEM_UNAVAILABLE",
|
|
640
|
-
"E_REMOTE_ERROR",
|
|
641
|
-
"E_NETWORK_ERROR",
|
|
642
|
-
"E_SERVICE_ERROR",
|
|
643
|
-
"E_RECEIPT_FAILED",
|
|
644
|
-
"E_RECEIPT_FINISHED_FAILED"
|
|
645
|
-
]
|
|
358
|
+
lockQueue.sync {
|
|
359
|
+
validProducts.removeAll()
|
|
360
|
+
}
|
|
646
361
|
|
|
647
|
-
|
|
648
|
-
return descriptions[0]
|
|
362
|
+
resolve(nil)
|
|
649
363
|
}
|
|
650
364
|
|
|
651
|
-
|
|
652
|
-
|
|
365
|
+
@objc public func promotedProduct(
|
|
366
|
+
_ resolve: @escaping RCTPromiseResolveBlock = { _ in },
|
|
367
|
+
reject: @escaping RCTPromiseRejectBlock = { _, _, _ in }
|
|
368
|
+
) {
|
|
369
|
+
debugMessage("get promoted product")
|
|
370
|
+
resolve((promotedProduct != nil) ? getProductObject(promotedProduct!) : nil)
|
|
653
371
|
}
|
|
654
372
|
|
|
655
|
-
|
|
656
|
-
|
|
373
|
+
@objc public func buyPromotedProduct(
|
|
374
|
+
_ resolve: @escaping RCTPromiseResolveBlock = { _ in },
|
|
375
|
+
reject: @escaping RCTPromiseRejectBlock = { _, _, _ in }
|
|
376
|
+
) {
|
|
377
|
+
if let promoPayment = promotedPayment {
|
|
378
|
+
debugMessage("buy promoted product")
|
|
379
|
+
SKPaymentQueue.default().add(promoPayment)
|
|
380
|
+
} else {
|
|
381
|
+
reject("E_DEVELOPER_ERROR", "Invalid product ID.", nil)
|
|
382
|
+
}
|
|
383
|
+
}
|
|
657
384
|
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
385
|
+
@objc public func requestReceipt(
|
|
386
|
+
_ refresh: Bool,
|
|
387
|
+
resolve: @escaping RCTPromiseResolveBlock = { _ in },
|
|
388
|
+
reject: @escaping RCTPromiseRejectBlock = { _, _, _ in }
|
|
389
|
+
) {
|
|
390
|
+
requestReceiptData(withBlock: refresh) { [self] receiptData, error in
|
|
391
|
+
if error == nil {
|
|
392
|
+
resolve(receiptData?.base64EncodedString(options: []))
|
|
393
|
+
} else {
|
|
394
|
+
reject(standardErrorCode(9), "Invalid receipt", nil)
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
662
398
|
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
399
|
+
@objc public func finishTransaction(
|
|
400
|
+
_ transactionIdentifier: String,
|
|
401
|
+
resolve: @escaping RCTPromiseResolveBlock = { _ in },
|
|
402
|
+
reject: @escaping RCTPromiseRejectBlock = { _, _, _ in }
|
|
403
|
+
) {
|
|
404
|
+
finishTransaction(withIdentifier: transactionIdentifier)
|
|
405
|
+
resolve(nil)
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
@objc public func getPendingTransactions (
|
|
409
|
+
_ resolve: @escaping RCTPromiseResolveBlock = { _ in },
|
|
410
|
+
reject: @escaping RCTPromiseRejectBlock = { _, _, _ in }
|
|
411
|
+
) {
|
|
412
|
+
requestReceiptData(withBlock: false) { receiptData, _ in
|
|
413
|
+
var output: [AnyHashable] = []
|
|
414
|
+
|
|
415
|
+
if let receipt = receiptData {
|
|
416
|
+
let transactions = SKPaymentQueue.default().transactions
|
|
417
|
+
|
|
418
|
+
for item in transactions {
|
|
419
|
+
let timestamp = item.transactionDate?.millisecondsSince1970 == nil ? nil : String(item.transactionDate!.millisecondsSince1970)
|
|
420
|
+
let purchase = [
|
|
421
|
+
"transactionDate": timestamp,
|
|
422
|
+
"transactionId": item.transactionIdentifier,
|
|
423
|
+
"productId": item.payment.productIdentifier,
|
|
424
|
+
"quantity": "\(item.payment.quantity)",
|
|
425
|
+
"transactionReceipt": receipt.base64EncodedString(options: [])
|
|
426
|
+
]
|
|
427
|
+
|
|
428
|
+
output.append(purchase)
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
resolve(output)
|
|
433
|
+
}
|
|
434
|
+
}
|
|
666
435
|
|
|
667
|
-
|
|
668
|
-
|
|
436
|
+
@objc public func presentCodeRedemptionSheet(
|
|
437
|
+
_ resolve: @escaping RCTPromiseResolveBlock = { _ in },
|
|
438
|
+
reject: @escaping RCTPromiseRejectBlock = { _, _, _ in }
|
|
439
|
+
) {
|
|
440
|
+
#if !os(tvOS)
|
|
441
|
+
if #available(iOS 14.0, tvOS 14.0, *) {
|
|
442
|
+
SKPaymentQueue.default().presentCodeRedemptionSheet()
|
|
443
|
+
resolve(nil)
|
|
444
|
+
} else {
|
|
445
|
+
reject(standardErrorCode(2), "This method only available above iOS 14", nil)
|
|
446
|
+
}
|
|
447
|
+
#else
|
|
448
|
+
reject(standardErrorCode(2), "This method is not available on tvOS", nil)
|
|
449
|
+
#endif
|
|
450
|
+
}
|
|
669
451
|
|
|
670
|
-
|
|
452
|
+
// StoreKitDelegate
|
|
453
|
+
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
|
|
454
|
+
for prod in response.products {
|
|
455
|
+
add(prod)
|
|
456
|
+
}
|
|
671
457
|
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
var periodNumberIOS = "0"
|
|
675
|
-
var periodUnitIOS = ""
|
|
676
|
-
var itemType = "iap"
|
|
458
|
+
var items: [[String: Any?]] = [[:]]
|
|
459
|
+
let lockQueue = DispatchQueue(label: "validProducts")
|
|
677
460
|
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
461
|
+
lockQueue.sync {
|
|
462
|
+
for product in validProducts {
|
|
463
|
+
items.append(getProductObject(product))
|
|
464
|
+
}
|
|
465
|
+
}
|
|
681
466
|
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
} else if unit == .month {
|
|
685
|
-
periodUnitIOS = "MONTH"
|
|
686
|
-
} else if unit == .week {
|
|
687
|
-
periodUnitIOS = "WEEK"
|
|
688
|
-
} else if unit == .day {
|
|
689
|
-
periodUnitIOS = "DAY"
|
|
690
|
-
}
|
|
467
|
+
resolvePromises(forKey: request.key, value: items)
|
|
468
|
+
}
|
|
691
469
|
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
470
|
+
// Add to valid products from Apple server response. Allowing getProducts, getSubscriptions call several times.
|
|
471
|
+
// Doesn't allow duplication. Replace new product.
|
|
472
|
+
func add(_ aProd: SKProduct) {
|
|
473
|
+
let lockQueue = DispatchQueue(label: "validProducts")
|
|
696
474
|
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
formatter.locale = product.introductoryPrice?.priceLocale
|
|
701
|
-
|
|
702
|
-
if let price = product.introductoryPrice?.price {
|
|
703
|
-
introductoryPrice = formatter.string(from: price)
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
introductoryPriceAsAmountIOS = product.introductoryPrice?.price.stringValue ?? ""
|
|
707
|
-
|
|
708
|
-
switch product.introductoryPrice?.paymentMode {
|
|
709
|
-
case .freeTrial:
|
|
710
|
-
introductoryPricePaymentMode = "FREETRIAL"
|
|
711
|
-
introductoryPriceNumberOfPeriods = NSNumber(value: product.introductoryPrice?.subscriptionPeriod.numberOfUnits ?? 0).stringValue
|
|
475
|
+
lockQueue.sync {
|
|
476
|
+
debugMessage("Add new object: \(aProd.productIdentifier)")
|
|
477
|
+
var delTar = -1
|
|
712
478
|
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
introductoryPriceNumberOfPeriods = NSNumber(value: product.introductoryPrice?.subscriptionPeriod.numberOfUnits ?? 0).stringValue
|
|
479
|
+
for k in 0..<validProducts.count {
|
|
480
|
+
let cur = validProducts[k]
|
|
481
|
+
if cur.productIdentifier == aProd.productIdentifier {
|
|
482
|
+
delTar = k
|
|
483
|
+
}
|
|
484
|
+
}
|
|
720
485
|
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
if product.introductoryPrice?.subscriptionPeriod.unit == .day {
|
|
727
|
-
introductoryPriceSubscriptionPeriod = "DAY"
|
|
728
|
-
} else if product.introductoryPrice?.subscriptionPeriod.unit == .week {
|
|
729
|
-
introductoryPriceSubscriptionPeriod = "WEEK"
|
|
730
|
-
} else if product.introductoryPrice?.subscriptionPeriod.unit == .month {
|
|
731
|
-
introductoryPriceSubscriptionPeriod = "MONTH"
|
|
732
|
-
} else if product.introductoryPrice?.subscriptionPeriod.unit == .year {
|
|
733
|
-
introductoryPriceSubscriptionPeriod = "YEAR"
|
|
734
|
-
} else {
|
|
735
|
-
introductoryPriceSubscriptionPeriod = ""
|
|
736
|
-
}
|
|
737
|
-
} else {
|
|
738
|
-
introductoryPrice = ""
|
|
739
|
-
introductoryPriceAsAmountIOS = ""
|
|
740
|
-
introductoryPricePaymentMode = ""
|
|
741
|
-
introductoryPriceNumberOfPeriods = ""
|
|
742
|
-
introductoryPriceSubscriptionPeriod = ""
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
if #available(iOS 10.0, tvOS 10.0, *) {
|
|
747
|
-
currencyCode = product.priceLocale.currencyCode
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
if #available(iOS 13.0, tvOS 13.0, *) {
|
|
751
|
-
countryCode = SKPaymentQueue.default().storefront?.countryCode
|
|
752
|
-
} else if #available(iOS 10.0, tvOS 10.0, *) {
|
|
753
|
-
countryCode = product.priceLocale.regionCode
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
var discounts: [[String: String?]]?
|
|
757
|
-
|
|
758
|
-
if #available(iOS 12.2, tvOS 12.2, *) {
|
|
759
|
-
discounts = getDiscountData(product)
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
let obj: [String: Any?] = [
|
|
763
|
-
"productId": product.productIdentifier,
|
|
764
|
-
"price": "\(product.price)",
|
|
765
|
-
"currency": currencyCode,
|
|
766
|
-
"countryCode": countryCode ?? "",
|
|
767
|
-
"type": itemType,
|
|
768
|
-
"title": product.localizedTitle != "" ? product.localizedTitle : "",
|
|
769
|
-
"description": product.localizedDescription != "" ? product.localizedDescription : "",
|
|
770
|
-
"localizedPrice": localizedPrice,
|
|
771
|
-
"subscriptionPeriodNumberIOS": periodNumberIOS,
|
|
772
|
-
"subscriptionPeriodUnitIOS": periodUnitIOS,
|
|
773
|
-
"introductoryPrice": introductoryPrice,
|
|
774
|
-
"introductoryPriceAsAmountIOS": introductoryPriceAsAmountIOS,
|
|
775
|
-
"introductoryPricePaymentModeIOS": introductoryPricePaymentMode,
|
|
776
|
-
"introductoryPriceNumberOfPeriodsIOS": introductoryPriceNumberOfPeriods,
|
|
777
|
-
"introductoryPriceSubscriptionPeriodIOS": introductoryPriceSubscriptionPeriod,
|
|
778
|
-
"discounts": discounts
|
|
779
|
-
]
|
|
780
|
-
|
|
781
|
-
return obj
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
func getDiscountData(_ product: SKProduct) -> [[String: String?]]? {
|
|
785
|
-
if #available(iOS 12.2, tvOS 12.2, *) {
|
|
786
|
-
var mappedDiscounts: [[String: String?]] = []
|
|
787
|
-
var localizedPrice: String?
|
|
788
|
-
var paymendMode: String?
|
|
789
|
-
var subscriptionPeriods: String?
|
|
790
|
-
var discountType: String?
|
|
791
|
-
|
|
792
|
-
for discount in product.discounts {
|
|
793
|
-
let formatter = NumberFormatter()
|
|
794
|
-
formatter.numberStyle = .currency
|
|
795
|
-
let priceLocale: Locale? = discount.priceLocale
|
|
796
|
-
if let pLocale = priceLocale {
|
|
797
|
-
formatter.locale = pLocale
|
|
486
|
+
if delTar >= 0 {
|
|
487
|
+
validProducts.remove(at: delTar)
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
validProducts.append(aProd)
|
|
798
491
|
}
|
|
799
|
-
|
|
800
|
-
var numberOfPeriods: String?
|
|
492
|
+
}
|
|
801
493
|
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
494
|
+
func request(_ request: SKRequest, didFailWithError error: Error) {
|
|
495
|
+
let nsError = error as NSError
|
|
496
|
+
|
|
497
|
+
if request is SKReceiptRefreshRequest {
|
|
498
|
+
if let unwrappedReceiptBlock = receiptBlock {
|
|
499
|
+
let standardError = NSError(domain: nsError.domain, code: 9, userInfo: nsError.userInfo)
|
|
500
|
+
unwrappedReceiptBlock(nil, standardError)
|
|
501
|
+
receiptBlock = nil
|
|
502
|
+
return
|
|
503
|
+
} else {
|
|
504
|
+
if let key: String = productsRequest?.key {
|
|
505
|
+
myQueue.sync(execute: { [self] in
|
|
506
|
+
rejectPromises(
|
|
507
|
+
forKey: key,
|
|
508
|
+
code: standardErrorCode(nsError.code),
|
|
509
|
+
message: error.localizedDescription,
|
|
510
|
+
error: error)}
|
|
511
|
+
)
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
807
516
|
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
517
|
+
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
|
|
518
|
+
for transaction in transactions {
|
|
519
|
+
switch transaction.transactionState {
|
|
520
|
+
case .purchasing:
|
|
521
|
+
debugMessage("Purchase Started")
|
|
522
|
+
break
|
|
523
|
+
|
|
524
|
+
case .purchased:
|
|
525
|
+
debugMessage("Purchase Successful")
|
|
526
|
+
purchaseProcess(transaction)
|
|
527
|
+
break
|
|
528
|
+
|
|
529
|
+
case .restored:
|
|
530
|
+
debugMessage("Restored")
|
|
531
|
+
SKPaymentQueue.default().finishTransaction(transaction)
|
|
532
|
+
break
|
|
533
|
+
|
|
534
|
+
case .deferred:
|
|
535
|
+
debugMessage("Deferred (awaiting approval via parental controls, etc.)")
|
|
536
|
+
|
|
537
|
+
myQueue.sync(execute: { [self] in
|
|
538
|
+
if hasListeners {
|
|
539
|
+
let err = [
|
|
540
|
+
"debugMessage": "The payment was deferred (awaiting approval via parental controls for instance)",
|
|
541
|
+
"code": "E_DEFERRED_PAYMENT",
|
|
542
|
+
"message": "The payment was deferred (awaiting approval via parental controls for instance)",
|
|
543
|
+
"productId": transaction.payment.productIdentifier,
|
|
544
|
+
"quantity": "\(transaction.payment.quantity)"
|
|
545
|
+
]
|
|
546
|
+
|
|
547
|
+
sendEvent(withName: "purchase-error", body: err)
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
rejectPromises(
|
|
551
|
+
forKey: transaction.payment.productIdentifier,
|
|
552
|
+
code: "E_DEFERRED_PAYMENT",
|
|
553
|
+
message: "The payment was deferred (awaiting approval via parental controls for instance)",
|
|
554
|
+
error: nil)
|
|
555
|
+
})
|
|
556
|
+
|
|
557
|
+
case .failed:
|
|
558
|
+
debugMessage("Purchase Failed")
|
|
559
|
+
|
|
560
|
+
SKPaymentQueue.default().finishTransaction(transaction)
|
|
561
|
+
|
|
562
|
+
myQueue.sync(execute: { [self] in
|
|
563
|
+
var nsError = transaction.error as? NSError
|
|
564
|
+
// From https://developer.apple.com/forums/thread/674081
|
|
565
|
+
if let underlyingError = nsError?.userInfo["NSUnderlyingError"] as? NSError,
|
|
566
|
+
underlyingError.code == 3038 {
|
|
567
|
+
// General conditions have changed, don't display an error for the interrupted transaction
|
|
568
|
+
nsError = underlyingError
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
if hasListeners {
|
|
572
|
+
let code = nsError?.code
|
|
573
|
+
let responseCode = String(code ?? 0)
|
|
574
|
+
let err = [
|
|
575
|
+
"responseCode": responseCode,
|
|
576
|
+
"debugMessage": transaction.error?.localizedDescription,
|
|
577
|
+
"code": standardErrorCode(code),
|
|
578
|
+
"message": transaction.error?.localizedDescription,
|
|
579
|
+
"productId": transaction.payment.productIdentifier
|
|
580
|
+
]
|
|
581
|
+
|
|
582
|
+
sendEvent(withName: "purchase-error", body: err)
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
rejectPromises(
|
|
586
|
+
forKey: transaction.payment.productIdentifier,
|
|
587
|
+
code: standardErrorCode(nsError?.code),
|
|
588
|
+
message: nsError?.localizedDescription,
|
|
589
|
+
error: nsError)
|
|
590
|
+
})
|
|
591
|
+
|
|
592
|
+
break
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
812
596
|
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
numberOfPeriods = NSNumber(value: discount.subscriptionPeriod.numberOfUnits ).stringValue
|
|
816
|
-
break
|
|
597
|
+
func finishTransaction(withIdentifier transactionIdentifier: String?) {
|
|
598
|
+
let queue = SKPaymentQueue.default()
|
|
817
599
|
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
600
|
+
for transaction in queue.transactions {
|
|
601
|
+
if transaction.transactionIdentifier == transactionIdentifier {
|
|
602
|
+
SKPaymentQueue.default().finishTransaction(transaction)
|
|
603
|
+
}
|
|
822
604
|
}
|
|
605
|
+
}
|
|
823
606
|
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
607
|
+
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
|
|
608
|
+
debugMessage("PaymentQueueRestoreCompletedTransactionsFinished")
|
|
609
|
+
var items = [[String: Any?]]()
|
|
827
610
|
|
|
828
|
-
|
|
829
|
-
|
|
611
|
+
for transaction in queue.transactions {
|
|
612
|
+
if transaction.transactionState == .restored || transaction.transactionState == .purchased {
|
|
613
|
+
getPurchaseData(transaction) { restored in
|
|
614
|
+
if let restored = restored {
|
|
615
|
+
items.append(restored)
|
|
616
|
+
}
|
|
830
617
|
|
|
831
|
-
|
|
832
|
-
|
|
618
|
+
SKPaymentQueue.default().finishTransaction(transaction)
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
833
622
|
|
|
834
|
-
|
|
835
|
-
|
|
623
|
+
resolvePromises(forKey: "availableItems", value: items)
|
|
624
|
+
}
|
|
836
625
|
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
626
|
+
func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: Error) {
|
|
627
|
+
myQueue.sync(execute: { [self] in
|
|
628
|
+
rejectPromises(
|
|
629
|
+
forKey: "availableItems",
|
|
630
|
+
code: standardErrorCode((error as NSError).code),
|
|
631
|
+
message: error.localizedDescription,
|
|
632
|
+
error: error)
|
|
633
|
+
})
|
|
840
634
|
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
635
|
+
debugMessage("restoreCompletedTransactionsFailedWithError")
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
func purchaseProcess(_ transaction: SKPaymentTransaction) {
|
|
639
|
+
if pendingTransactionWithAutoFinish {
|
|
640
|
+
SKPaymentQueue.default().finishTransaction(transaction)
|
|
641
|
+
pendingTransactionWithAutoFinish = false
|
|
642
|
+
}
|
|
846
643
|
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
break
|
|
644
|
+
getPurchaseData(transaction) { [self] purchase in
|
|
645
|
+
resolvePromises(forKey: transaction.payment.productIdentifier, value: purchase)
|
|
850
646
|
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
647
|
+
// additionally send event
|
|
648
|
+
if hasListeners {
|
|
649
|
+
sendEvent(withName: "purchase-updated", body: purchase)
|
|
650
|
+
}
|
|
854
651
|
}
|
|
652
|
+
}
|
|
855
653
|
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
654
|
+
func standardErrorCode(_ code: Int?) -> String? {
|
|
655
|
+
let descriptions = [
|
|
656
|
+
"E_UNKNOWN",
|
|
657
|
+
"E_SERVICE_ERROR",
|
|
658
|
+
"E_USER_CANCELLED",
|
|
659
|
+
"E_USER_ERROR",
|
|
660
|
+
"E_USER_ERROR",
|
|
661
|
+
"E_ITEM_UNAVAILABLE",
|
|
662
|
+
"E_REMOTE_ERROR",
|
|
663
|
+
"E_NETWORK_ERROR",
|
|
664
|
+
"E_SERVICE_ERROR",
|
|
665
|
+
"E_RECEIPT_FAILED",
|
|
666
|
+
"E_RECEIPT_FINISHED_FAILED"
|
|
864
667
|
]
|
|
865
668
|
|
|
866
|
-
|
|
867
|
-
|
|
669
|
+
guard let code = code else {
|
|
670
|
+
return descriptions[0]
|
|
671
|
+
}
|
|
672
|
+
if code == 3038 { // Purchase interrupted so user can accept terms and conditions
|
|
673
|
+
return "E_INTERRUPTED"
|
|
674
|
+
}
|
|
868
675
|
|
|
869
|
-
|
|
676
|
+
if code > descriptions.count - 1 || code < 0 { // Fix crash app without internet connection
|
|
677
|
+
return descriptions[0]
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
return descriptions[code]
|
|
870
681
|
}
|
|
871
682
|
|
|
872
|
-
|
|
873
|
-
|
|
683
|
+
func getProductObject(_ product: SKProduct) -> [String: Any?] {
|
|
684
|
+
let formatter = NumberFormatter()
|
|
685
|
+
formatter.numberStyle = .currency
|
|
686
|
+
formatter.locale = product.priceLocale
|
|
687
|
+
|
|
688
|
+
let localizedPrice = formatter.string(from: product.price)
|
|
689
|
+
var introductoryPrice = localizedPrice
|
|
690
|
+
var introductoryPriceAsAmountIOS = "\(product.price)"
|
|
691
|
+
|
|
692
|
+
var introductoryPricePaymentMode = ""
|
|
693
|
+
var introductoryPriceNumberOfPeriods = ""
|
|
694
|
+
|
|
695
|
+
var introductoryPriceSubscriptionPeriod = ""
|
|
696
|
+
|
|
697
|
+
var currencyCode: String? = ""
|
|
698
|
+
var countryCode: String? = ""
|
|
699
|
+
var periodNumberIOS = "0"
|
|
700
|
+
var periodUnitIOS = ""
|
|
701
|
+
var itemType = "iap"
|
|
702
|
+
|
|
703
|
+
if #available(iOS 11.2, tvOS 11.2, *) {
|
|
704
|
+
let numOfUnits = UInt(product.subscriptionPeriod?.numberOfUnits ?? 0)
|
|
705
|
+
let unit = product.subscriptionPeriod?.unit
|
|
706
|
+
|
|
707
|
+
if unit == .year {
|
|
708
|
+
periodUnitIOS = "YEAR"
|
|
709
|
+
} else if unit == .month {
|
|
710
|
+
periodUnitIOS = "MONTH"
|
|
711
|
+
} else if unit == .week {
|
|
712
|
+
periodUnitIOS = "WEEK"
|
|
713
|
+
} else if unit == .day {
|
|
714
|
+
periodUnitIOS = "DAY"
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
periodNumberIOS = String(format: "%lu", numOfUnits)
|
|
718
|
+
if numOfUnits != 0 {
|
|
719
|
+
itemType = "subs"
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// subscriptionPeriod = product.subscriptionPeriod ? [product.subscriptionPeriod stringValue] : @"";
|
|
723
|
+
// introductoryPrice = product.introductoryPrice != nil ? [NSString stringWithFormat:@"%@", product.introductoryPrice] : @"";
|
|
724
|
+
if product.introductoryPrice != nil {
|
|
725
|
+
formatter.locale = product.introductoryPrice?.priceLocale
|
|
726
|
+
|
|
727
|
+
if let price = product.introductoryPrice?.price {
|
|
728
|
+
introductoryPrice = formatter.string(from: price)
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
introductoryPriceAsAmountIOS = product.introductoryPrice?.price.stringValue ?? ""
|
|
732
|
+
|
|
733
|
+
switch product.introductoryPrice?.paymentMode {
|
|
734
|
+
case .freeTrial:
|
|
735
|
+
introductoryPricePaymentMode = "FREETRIAL"
|
|
736
|
+
introductoryPriceNumberOfPeriods = NSNumber(value: product.introductoryPrice?.subscriptionPeriod.numberOfUnits ?? 0).stringValue
|
|
737
|
+
|
|
738
|
+
case .payAsYouGo:
|
|
739
|
+
introductoryPricePaymentMode = "PAYASYOUGO"
|
|
740
|
+
introductoryPriceNumberOfPeriods = NSNumber(value: product.introductoryPrice?.numberOfPeriods ?? 0).stringValue
|
|
741
|
+
|
|
742
|
+
case .payUpFront:
|
|
743
|
+
introductoryPricePaymentMode = "PAYUPFRONT"
|
|
744
|
+
introductoryPriceNumberOfPeriods = NSNumber(value: product.introductoryPrice?.subscriptionPeriod.numberOfUnits ?? 0).stringValue
|
|
745
|
+
|
|
746
|
+
default:
|
|
747
|
+
introductoryPricePaymentMode = ""
|
|
748
|
+
introductoryPriceNumberOfPeriods = "0"
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
if product.introductoryPrice?.subscriptionPeriod.unit == .day {
|
|
752
|
+
introductoryPriceSubscriptionPeriod = "DAY"
|
|
753
|
+
} else if product.introductoryPrice?.subscriptionPeriod.unit == .week {
|
|
754
|
+
introductoryPriceSubscriptionPeriod = "WEEK"
|
|
755
|
+
} else if product.introductoryPrice?.subscriptionPeriod.unit == .month {
|
|
756
|
+
introductoryPriceSubscriptionPeriod = "MONTH"
|
|
757
|
+
} else if product.introductoryPrice?.subscriptionPeriod.unit == .year {
|
|
758
|
+
introductoryPriceSubscriptionPeriod = "YEAR"
|
|
759
|
+
} else {
|
|
760
|
+
introductoryPriceSubscriptionPeriod = ""
|
|
761
|
+
}
|
|
762
|
+
} else {
|
|
763
|
+
introductoryPrice = ""
|
|
764
|
+
introductoryPriceAsAmountIOS = ""
|
|
765
|
+
introductoryPricePaymentMode = ""
|
|
766
|
+
introductoryPriceNumberOfPeriods = ""
|
|
767
|
+
introductoryPriceSubscriptionPeriod = ""
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
if #available(iOS 10.0, tvOS 10.0, *) {
|
|
772
|
+
currencyCode = product.priceLocale.currencyCode
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
if #available(iOS 13.0, tvOS 13.0, *) {
|
|
776
|
+
countryCode = SKPaymentQueue.default().storefront?.countryCode
|
|
777
|
+
} else if #available(iOS 10.0, tvOS 10.0, *) {
|
|
778
|
+
countryCode = product.priceLocale.regionCode
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
var discounts: [[String: String?]]?
|
|
874
782
|
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
783
|
+
if #available(iOS 12.2, tvOS 12.2, *) {
|
|
784
|
+
discounts = getDiscountData(product)
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
let obj: [String: Any?] = [
|
|
788
|
+
"productId": product.productIdentifier,
|
|
789
|
+
"price": "\(product.price)",
|
|
790
|
+
"currency": currencyCode,
|
|
791
|
+
"countryCode": countryCode ?? "",
|
|
792
|
+
"type": itemType,
|
|
793
|
+
"title": product.localizedTitle != "" ? product.localizedTitle : "",
|
|
794
|
+
"description": product.localizedDescription != "" ? product.localizedDescription : "",
|
|
795
|
+
"localizedPrice": localizedPrice,
|
|
796
|
+
"subscriptionPeriodNumberIOS": periodNumberIOS,
|
|
797
|
+
"subscriptionPeriodUnitIOS": periodUnitIOS,
|
|
798
|
+
"introductoryPrice": introductoryPrice,
|
|
799
|
+
"introductoryPriceAsAmountIOS": introductoryPriceAsAmountIOS,
|
|
800
|
+
"introductoryPricePaymentModeIOS": introductoryPricePaymentMode,
|
|
801
|
+
"introductoryPriceNumberOfPeriodsIOS": introductoryPriceNumberOfPeriods,
|
|
802
|
+
"introductoryPriceSubscriptionPeriodIOS": introductoryPriceSubscriptionPeriod,
|
|
803
|
+
"discounts": discounts
|
|
885
804
|
]
|
|
886
805
|
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
806
|
+
return obj
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
func getDiscountData(_ product: SKProduct) -> [[String: String?]]? {
|
|
810
|
+
if #available(iOS 12.2, tvOS 12.2, *) {
|
|
811
|
+
var mappedDiscounts: [[String: String?]] = []
|
|
812
|
+
var localizedPrice: String?
|
|
813
|
+
var paymendMode: String?
|
|
814
|
+
var subscriptionPeriods: String?
|
|
815
|
+
var discountType: String?
|
|
816
|
+
|
|
817
|
+
for discount in product.discounts {
|
|
818
|
+
let formatter = NumberFormatter()
|
|
819
|
+
formatter.numberStyle = .currency
|
|
820
|
+
let priceLocale: Locale? = discount.priceLocale
|
|
821
|
+
if let pLocale = priceLocale {
|
|
822
|
+
formatter.locale = pLocale
|
|
823
|
+
}
|
|
824
|
+
localizedPrice = formatter.string(from: discount.price)
|
|
825
|
+
var numberOfPeriods: String?
|
|
826
|
+
|
|
827
|
+
switch discount.paymentMode {
|
|
828
|
+
case .freeTrial:
|
|
829
|
+
paymendMode = "FREETRIAL"
|
|
830
|
+
numberOfPeriods = NSNumber(value: discount.subscriptionPeriod.numberOfUnits ).stringValue
|
|
831
|
+
break
|
|
832
|
+
|
|
833
|
+
case .payAsYouGo:
|
|
834
|
+
paymendMode = "PAYASYOUGO"
|
|
835
|
+
numberOfPeriods = NSNumber(value: discount.numberOfPeriods).stringValue
|
|
836
|
+
break
|
|
837
|
+
|
|
838
|
+
case .payUpFront:
|
|
839
|
+
paymendMode = "PAYUPFRONT"
|
|
840
|
+
numberOfPeriods = NSNumber(value: discount.subscriptionPeriod.numberOfUnits ).stringValue
|
|
841
|
+
break
|
|
842
|
+
|
|
843
|
+
default:
|
|
844
|
+
paymendMode = ""
|
|
845
|
+
numberOfPeriods = "0"
|
|
846
|
+
break
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
switch discount.subscriptionPeriod.unit {
|
|
850
|
+
case .day:
|
|
851
|
+
subscriptionPeriods = "DAY"
|
|
852
|
+
|
|
853
|
+
case .week:
|
|
854
|
+
subscriptionPeriods = "WEEK"
|
|
855
|
+
|
|
856
|
+
case .month:
|
|
857
|
+
subscriptionPeriods = "MONTH"
|
|
858
|
+
|
|
859
|
+
case .year:
|
|
860
|
+
subscriptionPeriods = "YEAR"
|
|
861
|
+
|
|
862
|
+
default:
|
|
863
|
+
subscriptionPeriods = ""
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
let discountIdentifier = discount.identifier
|
|
867
|
+
switch discount.type {
|
|
868
|
+
case SKProductDiscount.Type.introductory:
|
|
869
|
+
discountType = "INTRODUCTORY"
|
|
870
|
+
break
|
|
871
|
+
|
|
872
|
+
case SKProductDiscount.Type.subscription:
|
|
873
|
+
discountType = "SUBSCRIPTION"
|
|
874
|
+
break
|
|
875
|
+
|
|
876
|
+
default:
|
|
877
|
+
discountType = ""
|
|
878
|
+
break
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
let discountObj = [
|
|
882
|
+
"identifier": discountIdentifier,
|
|
883
|
+
"type": discountType,
|
|
884
|
+
"numberOfPeriods": numberOfPeriods,
|
|
885
|
+
"price": "\(discount.price)",
|
|
886
|
+
"localizedPrice": localizedPrice,
|
|
887
|
+
"paymentMode": paymendMode,
|
|
888
|
+
"subscriptionPeriod": subscriptionPeriods
|
|
889
|
+
]
|
|
890
|
+
|
|
891
|
+
mappedDiscounts.append(discountObj)
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
return mappedDiscounts
|
|
891
895
|
}
|
|
892
896
|
|
|
893
|
-
|
|
894
|
-
|
|
897
|
+
return nil
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
func getPurchaseData(_ transaction: SKPaymentTransaction, withBlock block: @escaping (_ transactionDict: [String: Any]?) -> Void) {
|
|
901
|
+
requestReceiptData(withBlock: false) { receiptData, _ in
|
|
902
|
+
if receiptData == nil {
|
|
903
|
+
block(nil)
|
|
904
|
+
} else {
|
|
905
|
+
var purchase = [
|
|
906
|
+
"transactionDate": transaction.transactionDate?.millisecondsSince1970String,
|
|
907
|
+
"transactionId": transaction.transactionIdentifier,
|
|
908
|
+
"productId": transaction.payment.productIdentifier,
|
|
909
|
+
"transactionReceipt": receiptData?.base64EncodedString(options: [])
|
|
910
|
+
]
|
|
911
|
+
|
|
912
|
+
// originalTransaction is available for restore purchase and purchase of cancelled/expired subscriptions
|
|
913
|
+
if let originalTransaction = transaction.original {
|
|
914
|
+
purchase["originalTransactionDateIOS"] = originalTransaction.transactionDate?.millisecondsSince1970String
|
|
915
|
+
purchase["originalTransactionIdentifierIOS"] = originalTransaction.transactionIdentifier
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
block(purchase as [String: Any])
|
|
919
|
+
}
|
|
920
|
+
}
|
|
895
921
|
}
|
|
896
|
-
}
|
|
897
922
|
|
|
898
|
-
|
|
899
|
-
|
|
923
|
+
func requestReceiptData(withBlock forceRefresh: Bool, withBlock block: @escaping (_ data: Data?, _ error: Error?) -> Void) {
|
|
924
|
+
debugMessage("requestReceiptDataWithBlock with force refresh: \(forceRefresh ? "YES" : "NO")")
|
|
900
925
|
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
926
|
+
if forceRefresh || isReceiptPresent() == false {
|
|
927
|
+
let refreshRequest = SKReceiptRefreshRequest()
|
|
928
|
+
refreshRequest.delegate = self
|
|
929
|
+
refreshRequest.start()
|
|
930
|
+
receiptBlock = block
|
|
931
|
+
} else {
|
|
932
|
+
receiptBlock = nil
|
|
933
|
+
block(receiptData(), nil)
|
|
934
|
+
}
|
|
909
935
|
}
|
|
910
|
-
}
|
|
911
936
|
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
937
|
+
func isReceiptPresent() -> Bool {
|
|
938
|
+
let receiptURL = Bundle.main.appStoreReceiptURL
|
|
939
|
+
var canReachError: Error?
|
|
940
|
+
|
|
941
|
+
do {
|
|
942
|
+
try _ = receiptURL?.checkResourceIsReachable()
|
|
943
|
+
} catch let error {
|
|
944
|
+
canReachError = error
|
|
945
|
+
}
|
|
915
946
|
|
|
916
|
-
|
|
917
|
-
try _ = receiptURL?.checkResourceIsReachable()
|
|
918
|
-
} catch let error {
|
|
919
|
-
canReachError = error
|
|
947
|
+
return canReachError == nil
|
|
920
948
|
}
|
|
921
949
|
|
|
922
|
-
|
|
923
|
-
|
|
950
|
+
func receiptData() -> Data? {
|
|
951
|
+
let receiptURL = Bundle.main.appStoreReceiptURL
|
|
952
|
+
var receiptData: Data?
|
|
924
953
|
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
954
|
+
if let receiptURL = receiptURL {
|
|
955
|
+
do {
|
|
956
|
+
try receiptData = Data(contentsOf: receiptURL)
|
|
957
|
+
} catch _ {
|
|
958
|
+
}
|
|
959
|
+
}
|
|
928
960
|
|
|
929
|
-
|
|
930
|
-
do {
|
|
931
|
-
try receiptData = Data(contentsOf: receiptURL)
|
|
932
|
-
} catch _ {
|
|
933
|
-
}
|
|
961
|
+
return receiptData
|
|
934
962
|
}
|
|
935
963
|
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
if request is SKReceiptRefreshRequest {
|
|
941
|
-
if isReceiptPresent() == true {
|
|
942
|
-
debugMessage("Receipt refreshed success")
|
|
964
|
+
func requestDidFinish(_ request: SKRequest) {
|
|
965
|
+
if request is SKReceiptRefreshRequest {
|
|
966
|
+
if isReceiptPresent() == true {
|
|
967
|
+
debugMessage("Receipt refreshed success")
|
|
943
968
|
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
969
|
+
if let receiptBlock = receiptBlock {
|
|
970
|
+
receiptBlock(receiptData(), nil)
|
|
971
|
+
}
|
|
972
|
+
} else if let receiptBlock = receiptBlock {
|
|
973
|
+
debugMessage("Finished but receipt refreshed failed")
|
|
949
974
|
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
975
|
+
let error = NSError(domain: "Receipt request finished but it failed!", code: 10, userInfo: nil)
|
|
976
|
+
receiptBlock(nil, error)
|
|
977
|
+
}
|
|
953
978
|
|
|
954
|
-
|
|
979
|
+
receiptBlock = nil
|
|
980
|
+
}
|
|
955
981
|
}
|
|
956
|
-
}
|
|
957
982
|
|
|
958
|
-
|
|
959
|
-
|
|
983
|
+
func paymentQueue(_ queue: SKPaymentQueue, removedTransactions transactions: [SKPaymentTransaction]) {
|
|
984
|
+
debugMessage("removedTransactions - countPendingTransactions \(countPendingTransaction)")
|
|
960
985
|
|
|
961
|
-
|
|
962
|
-
|
|
986
|
+
if countPendingTransaction > 0 {
|
|
987
|
+
countPendingTransaction -= transactions.count
|
|
963
988
|
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
989
|
+
if countPendingTransaction <= 0 {
|
|
990
|
+
resolvePromises(forKey: "cleaningTransactions", value: nil)
|
|
991
|
+
countPendingTransaction = 0
|
|
992
|
+
}
|
|
993
|
+
}
|
|
968
994
|
}
|
|
969
|
-
}
|
|
970
995
|
}
|