react-native-iap 9.0.0-beta → 9.0.0-beta10

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