react-native-iap 8.5.2 → 8.5.3

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