react-native-iap 8.5.2 → 8.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -20
- package/RNIap.podspec +33 -18
- package/android/build.gradle +137 -38
- package/android/gradle.properties +8 -0
- package/ios/RNIapIos-Bridging-Header.h +2 -0
- package/ios/RNIapIos.m +13 -0
- package/ios/RNIapIos.swift +903 -881
- package/ios/{RNIap.xcodeproj → RNIapIos.xcodeproj}/project.pbxproj +29 -116
- package/lib/commonjs/__test__/iap.test.js +21 -0
- package/lib/commonjs/__test__/iap.test.js.map +1 -0
- package/lib/commonjs/hooks/useIAP.js +78 -0
- package/lib/commonjs/hooks/useIAP.js.map +1 -0
- package/lib/commonjs/hooks/withIAPContext.js +92 -0
- package/lib/commonjs/hooks/withIAPContext.js.map +1 -0
- package/lib/commonjs/iap.js +577 -0
- package/lib/commonjs/iap.js.map +1 -0
- package/lib/commonjs/index.js +59 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/types/amazon.js +2 -0
- package/lib/commonjs/types/amazon.js.map +1 -0
- package/lib/commonjs/types/android.js +55 -0
- package/lib/commonjs/types/android.js.map +1 -0
- package/lib/commonjs/types/apple.js +165 -0
- package/lib/commonjs/types/apple.js.map +1 -0
- package/lib/commonjs/types/index.js +59 -0
- package/lib/commonjs/types/index.js.map +1 -0
- package/lib/module/__test__/iap.test.js +17 -0
- package/lib/module/__test__/iap.test.js.map +1 -0
- package/lib/module/hooks/useIAP.js +68 -0
- package/lib/module/hooks/useIAP.js.map +1 -0
- package/lib/module/hooks/withIAPContext.js +76 -0
- package/lib/module/hooks/withIAPContext.js.map +1 -0
- package/lib/module/iap.js +488 -0
- package/lib/module/iap.js.map +1 -0
- package/lib/module/index.js +6 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/types/amazon.js +2 -0
- package/lib/module/types/amazon.js.map +1 -0
- package/lib/module/types/android.js +44 -0
- package/lib/module/types/android.js.map +1 -0
- package/lib/module/types/apple.js +153 -0
- package/lib/module/types/apple.js.map +1 -0
- package/lib/module/types/index.js +48 -0
- package/lib/module/types/index.js.map +1 -0
- package/{src → lib/typescript}/__test__/iap.test.d.ts +0 -0
- package/{src → lib/typescript}/hooks/useIAP.d.ts +1 -1
- package/{src → lib/typescript}/hooks/withIAPContext.d.ts +1 -1
- package/{src → lib/typescript}/iap.d.ts +10 -9
- package/{src → lib/typescript}/index.d.ts +2 -1
- package/{src → lib/typescript}/types/amazon.d.ts +0 -0
- package/{src → lib/typescript}/types/android.d.ts +0 -0
- package/{src → lib/typescript}/types/apple.d.ts +0 -0
- package/{src → lib/typescript}/types/index.d.ts +23 -6
- package/package.json +87 -56
- package/src/__test__/iap.test.ts +20 -0
- package/src/hooks/useIAP.ts +130 -0
- package/src/hooks/withIAPContext.tsx +160 -0
- package/src/iap.ts +686 -0
- package/src/{index.js → index.ts} +4 -1
- package/src/types/amazon.ts +23 -0
- package/src/types/android.ts +51 -0
- package/src/types/apple.ts +467 -0
- package/src/types/index.ts +185 -0
- package/.editorconfig +0 -10
- package/.flowconfig +0 -11
- package/.monolinterrc +0 -3
- package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/android/gradle/wrapper/gradle-wrapper.properties +0 -6
- package/android/gradlew +0 -160
- package/android/gradlew.bat +0 -90
- package/babel.config.js +0 -10
- package/index.d.ts +0 -3
- package/index.js +0 -3
- package/index.js.flow +0 -9
- package/ios/RNIap.xcodeproj/xcshareddata/xcschemes/RNIap.xcscheme +0 -80
- package/jest.config.js +0 -194
- package/src/__test__/iap.test.js +0 -59
- package/src/hooks/useIAP.js +0 -141
- package/src/hooks/withIAPContext.js +0 -150
- package/src/iap.js +0 -625
- package/src/types/amazon.js +0 -1
- package/src/types/android.js +0 -22
- package/src/types/apple.js +0 -165
- package/src/types/index.js +0 -40
- package/test/mocks/react-native-modules.js +0 -14
package/ios/RNIapIos.swift
CHANGED
|
@@ -1,932 +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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
var key: String {
|
|
31
|
+
return String(self.hashValue)
|
|
32
|
+
}
|
|
29
33
|
}
|
|
30
|
-
|
|
34
|
+
|
|
31
35
|
@objc(RNIapIos)
|
|
32
36
|
class RNIapIos: RCTEventEmitter, SKRequestDelegate, SKPaymentTransactionObserver, SKProductsRequestDelegate {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
}
|
|
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
|
-
}
|
|
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)
|
|
103
83
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
reject(code, message, error)
|
|
112
|
-
}
|
|
113
|
-
promisesByKey[key]=nil
|
|
114
|
-
}
|
|
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 = []
|
|
115
91
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
}
|
|
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)
|
|
163
127
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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)
|
|
213
213
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
-
}
|
|
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
|
+
}
|
|
303
232
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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
|
-
}
|
|
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)
|
|
357
261
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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)
|
|
373
296
|
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
@
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
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
|
-
}
|
|
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()
|
|
466
327
|
}
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
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)
|
|
488
396
|
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
resolve(output)
|
|
489
400
|
}
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
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
|
-
}
|
|
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)
|
|
555
423
|
}
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
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
|
+
}
|
|
564
432
|
}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
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
|
|
579
450
|
}
|
|
580
|
-
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if delTar >= 0 {
|
|
454
|
+
validProducts.remove(at: delTar)
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
validProducts.append(aProd)
|
|
581
458
|
}
|
|
582
|
-
|
|
583
|
-
|
|
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
|
+
|
|
504
|
+
myQueue.sync(execute: { [self] in
|
|
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)
|
|
522
|
+
})
|
|
523
|
+
|
|
524
|
+
case .failed:
|
|
525
|
+
debugMessage("Purchase Failed")
|
|
526
|
+
|
|
527
|
+
SKPaymentQueue.default().finishTransaction(transaction)
|
|
528
|
+
|
|
584
529
|
myQueue.sync(execute: { [self] in
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
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)
|
|
590
551
|
})
|
|
591
|
-
|
|
552
|
+
|
|
553
|
+
break
|
|
554
|
+
}
|
|
592
555
|
}
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
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)
|
|
606
580
|
}
|
|
581
|
+
}
|
|
607
582
|
}
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
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)
|
|
628
687
|
}
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
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 = ""
|
|
632
719
|
}
|
|
633
|
-
|
|
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
|
|
634
731
|
}
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
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 {
|
|
638
776
|
let formatter = NumberFormatter()
|
|
639
777
|
formatter.numberStyle = .currency
|
|
640
|
-
formatter.locale =
|
|
641
|
-
|
|
642
|
-
|
|
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
|
-
}
|
|
778
|
+
formatter.locale = discount.priceLocale
|
|
779
|
+
localizedPrice = formatter.string(from: discount.price)
|
|
780
|
+
var numberOfPeriods: String?
|
|
721
781
|
|
|
782
|
+
switch discount.paymentMode {
|
|
783
|
+
case .freeTrial:
|
|
784
|
+
paymendMode = "FREETRIAL"
|
|
785
|
+
numberOfPeriods = NSNumber(value: discount.subscriptionPeriod.numberOfUnits ).stringValue
|
|
786
|
+
break
|
|
722
787
|
|
|
788
|
+
case .payAsYouGo:
|
|
789
|
+
paymendMode = "PAYASYOUGO"
|
|
790
|
+
numberOfPeriods = NSNumber(value: discount.numberOfPeriods).stringValue
|
|
791
|
+
break
|
|
723
792
|
|
|
724
|
-
|
|
725
|
-
|
|
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
|
|
726
802
|
}
|
|
727
803
|
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
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 = ""
|
|
732
819
|
}
|
|
733
|
-
|
|
734
|
-
var discounts: [[String: String?]]?
|
|
735
820
|
|
|
736
|
-
|
|
737
|
-
|
|
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
|
|
738
834
|
}
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
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
|
|
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
|
|
758
844
|
]
|
|
759
|
-
|
|
760
|
-
|
|
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
|
-
}
|
|
845
|
+
|
|
846
|
+
mappedDiscounts.append(discountObj)
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
return mappedDiscounts
|
|
863
850
|
}
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
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
|
|
875
871
|
}
|
|
872
|
+
|
|
873
|
+
block(purchase as [String: Any])
|
|
874
|
+
}
|
|
876
875
|
}
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
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
|
-
}
|
|
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
|
|
916
900
|
}
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
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)
|
|
930
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
|
+
}
|
|
931
952
|
}
|
|
953
|
+
}
|
|
932
954
|
}
|