react-native-iap 8.2.1 → 8.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.yarn/install-state.gz +0 -0
- package/RNIap.podspec +5 -0
- package/android/build.gradle +1 -1
- package/android/src/amazon/java/com/dooboolab/RNIap/RNIapAmazonModule.kt +20 -2
- package/android/src/play/java/com/dooboolab/RNIap/RNIapModule.kt +150 -138
- package/android/src/testPlay/java/com/dooboolab/RNIap/RNIapModuleTest.kt +98 -7
- package/ios/RNIapIos.m +1 -0
- package/ios/RNIapIos.swift +3 -1
- package/ios/RNIapQueue.swift +4 -3
- package/package.json +1 -1
- package/src/hooks/useIAP.d.ts +1 -0
- package/src/hooks/useIAP.js +2 -1
- package/src/hooks/withIAPContext.d.ts +1 -0
- package/src/hooks/withIAPContext.js +9 -1
- package/src/iap.d.ts +2 -1
- package/src/iap.js +26 -18
- package/src/types/index.d.ts +2 -2
package/.yarn/install-state.gz
CHANGED
|
Binary file
|
package/RNIap.podspec
CHANGED
|
@@ -11,6 +11,11 @@ Pod::Spec.new do |s|
|
|
|
11
11
|
s.platforms = { :ios => "9.0", :tvos => "9.0" }
|
|
12
12
|
s.source = { :git => "https://github.com/dooboolab/react-native-iap.git", :tag => "#{s.version}" }
|
|
13
13
|
s.source_files = "ios/*.{h,m,swift}"
|
|
14
|
+
s.script_phase = {
|
|
15
|
+
:name => 'Copy Swift Header',
|
|
16
|
+
:script => 'ditto "${DERIVED_SOURCES_DIR}/${PRODUCT_MODULE_NAME}-Swift.h" "${PODS_ROOT}/Headers/Public/${PRODUCT_MODULE_NAME}/${PRODUCT_MODULE_NAME}-Swift.h"',
|
|
17
|
+
:execution_position => :after_compile
|
|
18
|
+
}
|
|
14
19
|
s.swift_version = "4.2"
|
|
15
20
|
s.requires_arc = true
|
|
16
21
|
|
package/android/build.gradle
CHANGED
|
@@ -66,7 +66,7 @@ repositories {
|
|
|
66
66
|
|
|
67
67
|
dependencies {
|
|
68
68
|
implementation 'com.facebook.react:react-native:+'
|
|
69
|
-
testImplementation 'junit:junit:4.
|
|
69
|
+
testImplementation 'junit:junit:4.13.1'
|
|
70
70
|
testImplementation "io.mockk:mockk:1.12.4"
|
|
71
71
|
playImplementation 'com.android.billingclient:billing:4.0.0'
|
|
72
72
|
def playServicesVersion = safeExtGet('playServicesVersion', DEFAULT_PLAY_SERVICES_VERSION)
|
|
@@ -2,6 +2,7 @@ package com.dooboolab.RNIap
|
|
|
2
2
|
|
|
3
3
|
import com.amazon.device.iap.PurchasingService
|
|
4
4
|
import com.amazon.device.iap.model.FulfillmentResult
|
|
5
|
+
import com.facebook.react.bridge.LifecycleEventListener
|
|
5
6
|
import com.facebook.react.bridge.Promise
|
|
6
7
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
7
8
|
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
@@ -10,9 +11,8 @@ import com.facebook.react.bridge.ReadableArray
|
|
|
10
11
|
import com.facebook.react.bridge.WritableNativeArray
|
|
11
12
|
import java.util.HashSet
|
|
12
13
|
|
|
13
|
-
class RNIapAmazonModule(reactContext: ReactApplicationContext
|
|
14
|
+
class RNIapAmazonModule(reactContext: ReactApplicationContext) :
|
|
14
15
|
ReactContextBaseJavaModule(reactContext) {
|
|
15
|
-
val TAG = "RNIapAmazonModule"
|
|
16
16
|
override fun getName(): String {
|
|
17
17
|
return TAG
|
|
18
18
|
}
|
|
@@ -127,5 +127,23 @@ class RNIapAmazonModule(reactContext: ReactApplicationContext?) :
|
|
|
127
127
|
const val PROMISE_QUERY_PURCHASES = "PROMISE_QUERY_PURCHASES"
|
|
128
128
|
const val PROMISE_QUERY_AVAILABLE_ITEMS = "PROMISE_QUERY_AVAILABLE_ITEMS"
|
|
129
129
|
const val PROMISE_GET_USER_DATA = "PROMISE_GET_USER_DATA"
|
|
130
|
+
|
|
131
|
+
const val TAG = "RNIapAmazonModule"
|
|
132
|
+
}
|
|
133
|
+
init {
|
|
134
|
+
val lifecycleEventListener: LifecycleEventListener = object : LifecycleEventListener {
|
|
135
|
+
/**
|
|
136
|
+
* From https://developer.amazon.com/docs/in-app-purchasing/iap-implement-iap.html#getpurchaseupdates-responses
|
|
137
|
+
* We should fetch updates on resume
|
|
138
|
+
*/
|
|
139
|
+
override fun onHostResume() {
|
|
140
|
+
PurchasingService.getUserData()
|
|
141
|
+
PurchasingService.getPurchaseUpdates(false)
|
|
142
|
+
}
|
|
143
|
+
override fun onHostPause() {}
|
|
144
|
+
override fun onHostDestroy() {
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
reactContext.addLifecycleEventListener(lifecycleEventListener)
|
|
130
148
|
}
|
|
131
149
|
}
|
|
@@ -22,6 +22,7 @@ import com.facebook.react.bridge.ReactContext
|
|
|
22
22
|
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
23
23
|
import com.facebook.react.bridge.ReactMethod
|
|
24
24
|
import com.facebook.react.bridge.ReadableArray
|
|
25
|
+
import com.facebook.react.bridge.ReadableType
|
|
25
26
|
import com.facebook.react.bridge.WritableMap
|
|
26
27
|
import com.facebook.react.bridge.WritableNativeArray
|
|
27
28
|
import com.facebook.react.bridge.WritableNativeMap
|
|
@@ -39,21 +40,30 @@ class RNIapModule(
|
|
|
39
40
|
ReactContextBaseJavaModule(reactContext),
|
|
40
41
|
PurchasesUpdatedListener {
|
|
41
42
|
|
|
42
|
-
private var
|
|
43
|
+
private var billingClientCache: BillingClient? = null
|
|
43
44
|
private val skus: MutableMap<String, SkuDetails> = mutableMapOf()
|
|
44
45
|
override fun getName(): String {
|
|
45
|
-
return
|
|
46
|
+
return TAG
|
|
46
47
|
}
|
|
47
48
|
|
|
48
|
-
internal fun ensureConnection(
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
internal fun ensureConnection(
|
|
50
|
+
promise: Promise,
|
|
51
|
+
callback: (billingClient: BillingClient) -> Unit
|
|
52
|
+
) {
|
|
53
|
+
val billingClient = billingClientCache
|
|
54
|
+
if (billingClient?.isReady == true) {
|
|
55
|
+
callback(billingClient)
|
|
51
56
|
return
|
|
52
57
|
} else {
|
|
53
58
|
val nested = PromiseImpl(
|
|
54
59
|
{
|
|
55
60
|
if (it.isNotEmpty() && it[0] is Boolean && it[0] as Boolean) {
|
|
56
|
-
|
|
61
|
+
val connectedBillingClient = billingClientCache
|
|
62
|
+
if (connectedBillingClient?.isReady == true) {
|
|
63
|
+
callback(connectedBillingClient)
|
|
64
|
+
} else {
|
|
65
|
+
promise.safeReject(DoobooUtils.E_NOT_PREPARED, "Unable to auto-initialize connection")
|
|
66
|
+
}
|
|
57
67
|
} else {
|
|
58
68
|
Log.i(TAG, "Incorrect parameter in resolve")
|
|
59
69
|
}
|
|
@@ -74,14 +84,6 @@ class RNIapModule(
|
|
|
74
84
|
|
|
75
85
|
@ReactMethod
|
|
76
86
|
fun initConnection(promise: Promise) {
|
|
77
|
-
if (billingClient.isReady) {
|
|
78
|
-
Log.i(
|
|
79
|
-
TAG,
|
|
80
|
-
"Already initialized, you should only call initConnection() once when your app starts"
|
|
81
|
-
)
|
|
82
|
-
promise.safeResolve(true)
|
|
83
|
-
return
|
|
84
|
-
}
|
|
85
87
|
if (googleApiAvailability.isGooglePlayServicesAvailable(reactContext)
|
|
86
88
|
!= ConnectionResult.SUCCESS
|
|
87
89
|
) {
|
|
@@ -90,30 +92,35 @@ class RNIapModule(
|
|
|
90
92
|
return
|
|
91
93
|
}
|
|
92
94
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
95
|
+
if (billingClientCache?.isReady == true) {
|
|
96
|
+
Log.i(
|
|
97
|
+
TAG,
|
|
98
|
+
"Already initialized, you should only call initConnection() once when your app starts"
|
|
99
|
+
)
|
|
100
|
+
promise.safeResolve(true)
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
builder.setListener(this).build().also {
|
|
104
|
+
billingClientCache = it
|
|
105
|
+
it.startConnection(
|
|
106
|
+
object : BillingClientStateListener {
|
|
107
|
+
override fun onBillingSetupFinished(billingResult: BillingResult) {
|
|
108
|
+
if (!isValidResult(billingResult, promise)) return
|
|
99
109
|
|
|
100
|
-
if (responseCode == BillingClient.BillingResponseCode.OK) {
|
|
101
110
|
promise.safeResolve(true)
|
|
102
|
-
} else {
|
|
103
|
-
PlayUtils.instance
|
|
104
|
-
.rejectPromiseWithBillingError(promise, responseCode)
|
|
105
111
|
}
|
|
106
|
-
}
|
|
107
112
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
113
|
+
override fun onBillingServiceDisconnected() {
|
|
114
|
+
Log.i(TAG, "Billing service disconnected")
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
}
|
|
112
118
|
}
|
|
113
119
|
|
|
114
120
|
@ReactMethod
|
|
115
121
|
fun endConnection(promise: Promise) {
|
|
116
|
-
|
|
122
|
+
billingClientCache?.endConnection()
|
|
123
|
+
billingClientCache = null
|
|
117
124
|
promise.safeResolve(true)
|
|
118
125
|
}
|
|
119
126
|
|
|
@@ -125,7 +132,7 @@ class RNIapModule(
|
|
|
125
132
|
for (purchase in purchases) {
|
|
126
133
|
ensureConnection(
|
|
127
134
|
promise
|
|
128
|
-
) {
|
|
135
|
+
) { billingClient ->
|
|
129
136
|
val consumeParams =
|
|
130
137
|
ConsumeParams.newBuilder().setPurchaseToken(purchase.purchaseToken)
|
|
131
138
|
.build()
|
|
@@ -151,10 +158,11 @@ class RNIapModule(
|
|
|
151
158
|
fun flushFailedPurchasesCachedAsPending(promise: Promise) {
|
|
152
159
|
ensureConnection(
|
|
153
160
|
promise
|
|
154
|
-
) {
|
|
161
|
+
) { billingClient ->
|
|
155
162
|
billingClient.queryPurchasesAsync(
|
|
156
163
|
BillingClient.SkuType.INAPP
|
|
157
|
-
) {
|
|
164
|
+
) { billingResult: BillingResult, list: List<Purchase>? ->
|
|
165
|
+
if (!isValidResult(billingResult, promise)) return@queryPurchasesAsync
|
|
158
166
|
if (list == null) {
|
|
159
167
|
// No purchases found
|
|
160
168
|
promise.safeResolve(false)
|
|
@@ -178,97 +186,109 @@ class RNIapModule(
|
|
|
178
186
|
}
|
|
179
187
|
|
|
180
188
|
@ReactMethod
|
|
181
|
-
fun getItemsByType(type: String
|
|
189
|
+
fun getItemsByType(type: String, skuArr: ReadableArray, promise: Promise) {
|
|
182
190
|
ensureConnection(
|
|
183
191
|
promise
|
|
184
|
-
) {
|
|
192
|
+
) { billingClient ->
|
|
185
193
|
val skuList = ArrayList<String>()
|
|
186
194
|
for (i in 0 until skuArr.size()) {
|
|
187
|
-
|
|
188
|
-
|
|
195
|
+
if (skuArr.getType(i) == ReadableType.String) {
|
|
196
|
+
val sku = skuArr.getString(i)
|
|
189
197
|
skuList.add(sku)
|
|
190
198
|
}
|
|
191
199
|
}
|
|
192
200
|
val params = SkuDetailsParams.newBuilder()
|
|
193
|
-
params.setSkusList(skuList).setType(type
|
|
201
|
+
params.setSkusList(skuList).setType(type)
|
|
194
202
|
billingClient.querySkuDetailsAsync(
|
|
195
203
|
params.build()
|
|
196
204
|
) { billingResult: BillingResult, skuDetailsList: List<SkuDetails>? ->
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
.rejectPromiseWithBillingError(promise, billingResult.responseCode)
|
|
201
|
-
return@querySkuDetailsAsync
|
|
202
|
-
}
|
|
205
|
+
if (!isValidResult(billingResult, promise)) return@querySkuDetailsAsync
|
|
206
|
+
|
|
207
|
+
val items = Arguments.createArray()
|
|
203
208
|
if (skuDetailsList != null) {
|
|
204
|
-
for (
|
|
205
|
-
skus[
|
|
209
|
+
for (skuDetails in skuDetailsList) {
|
|
210
|
+
skus[skuDetails.sku] = skuDetails
|
|
211
|
+
|
|
212
|
+
val item = Arguments.createMap()
|
|
213
|
+
item.putString("productId", skuDetails.sku)
|
|
214
|
+
val introductoryPriceMicros = skuDetails.introductoryPriceAmountMicros
|
|
215
|
+
val priceAmountMicros = skuDetails.priceAmountMicros
|
|
216
|
+
// Use valueOf instead of constructors.
|
|
217
|
+
// See:
|
|
218
|
+
// https://www.javaworld.com/article/2073176/caution--double-to-bigdecimal-in-java.html
|
|
219
|
+
val priceAmount = BigDecimal.valueOf(priceAmountMicros)
|
|
220
|
+
val introductoryPriceAmount =
|
|
221
|
+
BigDecimal.valueOf(introductoryPriceMicros)
|
|
222
|
+
val microUnitsDivisor = BigDecimal.valueOf(1000000)
|
|
223
|
+
val price = priceAmount.divide(microUnitsDivisor).toString()
|
|
224
|
+
val introductoryPriceAsAmountAndroid =
|
|
225
|
+
introductoryPriceAmount.divide(microUnitsDivisor).toString()
|
|
226
|
+
item.putString("price", price)
|
|
227
|
+
item.putString("currency", skuDetails.priceCurrencyCode)
|
|
228
|
+
item.putString("type", skuDetails.type)
|
|
229
|
+
item.putString("localizedPrice", skuDetails.price)
|
|
230
|
+
item.putString("title", skuDetails.title)
|
|
231
|
+
item.putString("description", skuDetails.description)
|
|
232
|
+
item.putString("introductoryPrice", skuDetails.introductoryPrice)
|
|
233
|
+
item.putString("typeAndroid", skuDetails.type)
|
|
234
|
+
item.putString("packageNameAndroid", skuDetails.zzc())
|
|
235
|
+
item.putString("originalPriceAndroid", skuDetails.originalPrice)
|
|
236
|
+
item.putString(
|
|
237
|
+
"subscriptionPeriodAndroid",
|
|
238
|
+
skuDetails.subscriptionPeriod
|
|
239
|
+
)
|
|
240
|
+
item.putString("freeTrialPeriodAndroid", skuDetails.freeTrialPeriod)
|
|
241
|
+
item.putString(
|
|
242
|
+
"introductoryPriceCyclesAndroid",
|
|
243
|
+
skuDetails.introductoryPriceCycles.toString()
|
|
244
|
+
)
|
|
245
|
+
item.putString(
|
|
246
|
+
"introductoryPricePeriodAndroid", skuDetails.introductoryPricePeriod
|
|
247
|
+
)
|
|
248
|
+
item.putString(
|
|
249
|
+
"introductoryPriceAsAmountAndroid", introductoryPriceAsAmountAndroid
|
|
250
|
+
)
|
|
251
|
+
item.putString("iconUrl", skuDetails.iconUrl)
|
|
252
|
+
item.putString("originalJson", skuDetails.originalJson)
|
|
253
|
+
val originalPriceAmountMicros =
|
|
254
|
+
BigDecimal.valueOf(skuDetails.originalPriceAmountMicros)
|
|
255
|
+
val originalPrice =
|
|
256
|
+
originalPriceAmountMicros.divide(microUnitsDivisor).toString()
|
|
257
|
+
item.putString("originalPrice", originalPrice)
|
|
258
|
+
items.pushMap(item)
|
|
206
259
|
}
|
|
207
260
|
}
|
|
208
|
-
val items = WritableNativeArray()
|
|
209
|
-
for (skuDetails in skuDetailsList!!) {
|
|
210
|
-
val item = Arguments.createMap()
|
|
211
|
-
item.putString("productId", skuDetails.sku)
|
|
212
|
-
val introductoryPriceMicros = skuDetails.introductoryPriceAmountMicros
|
|
213
|
-
val priceAmountMicros = skuDetails.priceAmountMicros
|
|
214
|
-
// Use valueOf instead of constructors.
|
|
215
|
-
// See:
|
|
216
|
-
// https://www.javaworld.com/article/2073176/caution--double-to-bigdecimal-in-java.html
|
|
217
|
-
val priceAmount = BigDecimal.valueOf(priceAmountMicros)
|
|
218
|
-
val introductoryPriceAmount =
|
|
219
|
-
BigDecimal.valueOf(introductoryPriceMicros)
|
|
220
|
-
val microUnitsDivisor = BigDecimal.valueOf(1000000)
|
|
221
|
-
val price = priceAmount.divide(microUnitsDivisor).toString()
|
|
222
|
-
val introductoryPriceAsAmountAndroid =
|
|
223
|
-
introductoryPriceAmount.divide(microUnitsDivisor).toString()
|
|
224
|
-
item.putString("price", price)
|
|
225
|
-
item.putString("currency", skuDetails.priceCurrencyCode)
|
|
226
|
-
item.putString("type", skuDetails.type)
|
|
227
|
-
item.putString("localizedPrice", skuDetails.price)
|
|
228
|
-
item.putString("title", skuDetails.title)
|
|
229
|
-
item.putString("description", skuDetails.description)
|
|
230
|
-
item.putString("introductoryPrice", skuDetails.introductoryPrice)
|
|
231
|
-
item.putString("typeAndroid", skuDetails.type)
|
|
232
|
-
item.putString("packageNameAndroid", skuDetails.zzc())
|
|
233
|
-
item.putString("originalPriceAndroid", skuDetails.originalPrice)
|
|
234
|
-
item.putString(
|
|
235
|
-
"subscriptionPeriodAndroid",
|
|
236
|
-
skuDetails.subscriptionPeriod
|
|
237
|
-
)
|
|
238
|
-
item.putString("freeTrialPeriodAndroid", skuDetails.freeTrialPeriod)
|
|
239
|
-
item.putString(
|
|
240
|
-
"introductoryPriceCyclesAndroid",
|
|
241
|
-
skuDetails.introductoryPriceCycles.toString()
|
|
242
|
-
)
|
|
243
|
-
item.putString(
|
|
244
|
-
"introductoryPricePeriodAndroid", skuDetails.introductoryPricePeriod
|
|
245
|
-
)
|
|
246
|
-
item.putString(
|
|
247
|
-
"introductoryPriceAsAmountAndroid", introductoryPriceAsAmountAndroid
|
|
248
|
-
)
|
|
249
|
-
item.putString("iconUrl", skuDetails.iconUrl)
|
|
250
|
-
item.putString("originalJson", skuDetails.originalJson)
|
|
251
|
-
val originalPriceAmountMicros =
|
|
252
|
-
BigDecimal.valueOf(skuDetails.originalPriceAmountMicros)
|
|
253
|
-
val originalPrice =
|
|
254
|
-
originalPriceAmountMicros.divide(microUnitsDivisor).toString()
|
|
255
|
-
item.putString("originalPrice", originalPrice)
|
|
256
|
-
items.pushMap(item)
|
|
257
|
-
}
|
|
258
261
|
promise.safeResolve(items)
|
|
259
262
|
}
|
|
260
263
|
}
|
|
261
264
|
}
|
|
262
265
|
|
|
266
|
+
/**
|
|
267
|
+
* Rejects promise with billing code if BillingResult is not OK
|
|
268
|
+
*/
|
|
269
|
+
private fun isValidResult(
|
|
270
|
+
billingResult: BillingResult,
|
|
271
|
+
promise: Promise
|
|
272
|
+
): Boolean {
|
|
273
|
+
Log.d(TAG, "responseCode: " + billingResult.responseCode)
|
|
274
|
+
if (billingResult.responseCode != BillingClient.BillingResponseCode.OK) {
|
|
275
|
+
PlayUtils.instance
|
|
276
|
+
.rejectPromiseWithBillingError(promise, billingResult.responseCode)
|
|
277
|
+
return false
|
|
278
|
+
}
|
|
279
|
+
return true
|
|
280
|
+
}
|
|
281
|
+
|
|
263
282
|
@ReactMethod
|
|
264
283
|
fun getAvailableItemsByType(type: String, promise: Promise) {
|
|
265
284
|
ensureConnection(
|
|
266
285
|
promise
|
|
267
|
-
) {
|
|
286
|
+
) { billingClient ->
|
|
268
287
|
val items = WritableNativeArray()
|
|
269
288
|
billingClient.queryPurchasesAsync(
|
|
270
289
|
if (type == "subs") BillingClient.SkuType.SUBS else BillingClient.SkuType.INAPP
|
|
271
|
-
) { billingResult: BillingResult
|
|
290
|
+
) { billingResult: BillingResult, purchases: List<Purchase>? ->
|
|
291
|
+
if (!isValidResult(billingResult, promise)) return@queryPurchasesAsync
|
|
272
292
|
if (purchases != null) {
|
|
273
293
|
for (i in purchases.indices) {
|
|
274
294
|
val purchase = purchases[i]
|
|
@@ -286,11 +306,11 @@ class RNIapModule(
|
|
|
286
306
|
item.putString("packageNameAndroid", purchase.packageName)
|
|
287
307
|
item.putString(
|
|
288
308
|
"obfuscatedAccountIdAndroid",
|
|
289
|
-
purchase.accountIdentifiers
|
|
309
|
+
purchase.accountIdentifiers?.obfuscatedAccountId
|
|
290
310
|
)
|
|
291
311
|
item.putString(
|
|
292
312
|
"obfuscatedProfileIdAndroid",
|
|
293
|
-
purchase.accountIdentifiers
|
|
313
|
+
purchase.accountIdentifiers?.obfuscatedProfileId
|
|
294
314
|
)
|
|
295
315
|
if (type == BillingClient.SkuType.SUBS) {
|
|
296
316
|
item.putBoolean("autoRenewingAndroid", purchase.isAutoRenewing)
|
|
@@ -307,20 +327,16 @@ class RNIapModule(
|
|
|
307
327
|
fun getPurchaseHistoryByType(type: String, promise: Promise) {
|
|
308
328
|
ensureConnection(
|
|
309
329
|
promise
|
|
310
|
-
) {
|
|
330
|
+
) { billingClient ->
|
|
311
331
|
billingClient.queryPurchaseHistoryAsync(
|
|
312
332
|
if (type == "subs") BillingClient.SkuType.SUBS else BillingClient.SkuType.INAPP
|
|
313
333
|
) { billingResult, purchaseHistoryRecordList ->
|
|
314
|
-
if (billingResult
|
|
315
|
-
|
|
316
|
-
.rejectPromiseWithBillingError(promise, billingResult.responseCode)
|
|
317
|
-
return@queryPurchaseHistoryAsync
|
|
318
|
-
}
|
|
334
|
+
if (!isValidResult(billingResult, promise)) return@queryPurchaseHistoryAsync
|
|
335
|
+
|
|
319
336
|
Log.d(TAG, purchaseHistoryRecordList.toString())
|
|
320
337
|
val items = Arguments.createArray()
|
|
321
|
-
|
|
338
|
+
purchaseHistoryRecordList?.forEach { purchase ->
|
|
322
339
|
val item = Arguments.createMap()
|
|
323
|
-
val purchase = purchaseHistoryRecordList[i]
|
|
324
340
|
item.putString("productId", purchase.skus[0])
|
|
325
341
|
item.putDouble("transactionDate", purchase.purchaseTime.toDouble())
|
|
326
342
|
item.putString("transactionReceipt", purchase.originalJson)
|
|
@@ -352,7 +368,7 @@ class RNIapModule(
|
|
|
352
368
|
}
|
|
353
369
|
ensureConnection(
|
|
354
370
|
promise
|
|
355
|
-
) {
|
|
371
|
+
) { billingClient ->
|
|
356
372
|
DoobooUtils.instance.addPromiseForKey(
|
|
357
373
|
PROMISE_BUY_ITEM, promise
|
|
358
374
|
)
|
|
@@ -436,32 +452,36 @@ class RNIapModule(
|
|
|
436
452
|
builder.setSubscriptionUpdateParams(subscriptionUpdateParams)
|
|
437
453
|
}
|
|
438
454
|
val flowParams = builder.build()
|
|
439
|
-
val
|
|
440
|
-
|
|
441
|
-
|
|
455
|
+
val billingResultCode = billingClient.launchBillingFlow(activity, flowParams)?.responseCode ?: BillingClient.BillingResponseCode.ERROR
|
|
456
|
+
if (billingResultCode == BillingClient.BillingResponseCode.OK) {
|
|
457
|
+
promise.safeResolve(true)
|
|
458
|
+
return@ensureConnection
|
|
459
|
+
} else {
|
|
460
|
+
val errorData: Array<String?> =
|
|
461
|
+
PlayUtils.instance.getBillingResponseData(billingResultCode)
|
|
462
|
+
promise.safeReject(errorData[0], errorData[1])
|
|
463
|
+
}
|
|
442
464
|
}
|
|
443
465
|
}
|
|
444
466
|
|
|
445
467
|
@ReactMethod
|
|
446
468
|
fun acknowledgePurchase(
|
|
447
|
-
token: String
|
|
469
|
+
token: String,
|
|
448
470
|
developerPayLoad: String?,
|
|
449
471
|
promise: Promise
|
|
450
472
|
) {
|
|
451
473
|
ensureConnection(
|
|
452
474
|
promise
|
|
453
|
-
) {
|
|
475
|
+
) { billingClient ->
|
|
454
476
|
val acknowledgePurchaseParams =
|
|
455
477
|
AcknowledgePurchaseParams.newBuilder().setPurchaseToken(
|
|
456
|
-
token
|
|
478
|
+
token
|
|
457
479
|
).build()
|
|
458
480
|
billingClient.acknowledgePurchase(
|
|
459
481
|
acknowledgePurchaseParams
|
|
460
482
|
) { billingResult: BillingResult ->
|
|
461
|
-
if (billingResult
|
|
462
|
-
|
|
463
|
-
.rejectPromiseWithBillingError(promise, billingResult.responseCode)
|
|
464
|
-
}
|
|
483
|
+
if (!isValidResult(billingResult, promise)) return@acknowledgePurchase
|
|
484
|
+
|
|
465
485
|
val map = Arguments.createMap()
|
|
466
486
|
map.putInt("responseCode", billingResult.responseCode)
|
|
467
487
|
map.putString("debugMessage", billingResult.debugMessage)
|
|
@@ -476,21 +496,18 @@ class RNIapModule(
|
|
|
476
496
|
|
|
477
497
|
@ReactMethod
|
|
478
498
|
fun consumeProduct(
|
|
479
|
-
token: String
|
|
499
|
+
token: String,
|
|
480
500
|
developerPayLoad: String?,
|
|
481
501
|
promise: Promise
|
|
482
502
|
) {
|
|
483
|
-
val params = ConsumeParams.newBuilder().setPurchaseToken(token
|
|
503
|
+
val params = ConsumeParams.newBuilder().setPurchaseToken(token).build()
|
|
484
504
|
ensureConnection(
|
|
485
505
|
promise
|
|
486
|
-
) {
|
|
506
|
+
) { billingClient ->
|
|
487
507
|
billingClient.consumeAsync(
|
|
488
508
|
params
|
|
489
509
|
) { billingResult: BillingResult, purchaseToken: String? ->
|
|
490
|
-
if (billingResult
|
|
491
|
-
PlayUtils.instance
|
|
492
|
-
.rejectPromiseWithBillingError(promise, billingResult.responseCode)
|
|
493
|
-
}
|
|
510
|
+
if (!isValidResult(billingResult, promise)) return@consumeAsync
|
|
494
511
|
|
|
495
512
|
val map = Arguments.createMap()
|
|
496
513
|
map.putInt("responseCode", billingResult.responseCode)
|
|
@@ -570,19 +587,15 @@ class RNIapModule(
|
|
|
570
587
|
private fun sendUnconsumedPurchases(promise: Promise) {
|
|
571
588
|
ensureConnection(
|
|
572
589
|
promise
|
|
573
|
-
) {
|
|
590
|
+
) { billingClient ->
|
|
574
591
|
val types = arrayOf(BillingClient.SkuType.INAPP, BillingClient.SkuType.SUBS)
|
|
575
592
|
for (type in types) {
|
|
576
593
|
billingClient.queryPurchasesAsync(
|
|
577
594
|
type
|
|
578
|
-
) { billingResult: BillingResult, list: List<Purchase
|
|
579
|
-
|
|
595
|
+
) { billingResult: BillingResult, list: List<Purchase> ->
|
|
596
|
+
if (!isValidResult(billingResult, promise)) return@queryPurchasesAsync
|
|
580
597
|
|
|
581
|
-
|
|
582
|
-
if (!purchase.isAcknowledged) {
|
|
583
|
-
unacknowledgedPurchases.add(purchase)
|
|
584
|
-
}
|
|
585
|
-
}
|
|
598
|
+
val unacknowledgedPurchases = list.filter { !it.isAcknowledged }
|
|
586
599
|
onPurchasesUpdated(billingResult, unacknowledgedPurchases)
|
|
587
600
|
}
|
|
588
601
|
}
|
|
@@ -605,9 +618,8 @@ class RNIapModule(
|
|
|
605
618
|
// Keep: Required for RN built-in Event Emitter Calls.
|
|
606
619
|
}
|
|
607
620
|
|
|
608
|
-
@
|
|
609
|
-
|
|
610
|
-
get() = reactApplicationContext.packageName
|
|
621
|
+
@ReactMethod
|
|
622
|
+
fun getPackageName(promise: Promise) = promise.resolve(reactApplicationContext.packageName)
|
|
611
623
|
|
|
612
624
|
private fun sendEvent(
|
|
613
625
|
reactContext: ReactContext,
|
|
@@ -629,7 +641,7 @@ class RNIapModule(
|
|
|
629
641
|
override fun onHostResume() {}
|
|
630
642
|
override fun onHostPause() {}
|
|
631
643
|
override fun onHostDestroy() {
|
|
632
|
-
|
|
644
|
+
billingClientCache?.endConnection()
|
|
633
645
|
}
|
|
634
646
|
}
|
|
635
647
|
reactContext.addLifecycleEventListener(lifecycleEventListener)
|
|
@@ -6,16 +6,26 @@ import com.android.billingclient.api.BillingResult
|
|
|
6
6
|
import com.android.billingclient.api.ConsumeResponseListener
|
|
7
7
|
import com.android.billingclient.api.Purchase
|
|
8
8
|
import com.android.billingclient.api.PurchasesResponseListener
|
|
9
|
+
import com.android.billingclient.api.SkuDetailsResponseListener
|
|
10
|
+
import com.facebook.react.bridge.Arguments
|
|
9
11
|
import com.facebook.react.bridge.Promise
|
|
10
12
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
13
|
+
import com.facebook.react.bridge.ReadableArray
|
|
14
|
+
import com.facebook.react.bridge.ReadableType
|
|
15
|
+
import com.facebook.react.bridge.WritableArray
|
|
16
|
+
import com.facebook.react.bridge.WritableMap
|
|
11
17
|
import com.google.android.gms.common.ConnectionResult
|
|
12
18
|
import com.google.android.gms.common.GoogleApiAvailability
|
|
13
19
|
import io.mockk.MockKAnnotations
|
|
14
20
|
import io.mockk.every
|
|
15
21
|
import io.mockk.impl.annotations.MockK
|
|
22
|
+
import io.mockk.just
|
|
16
23
|
import io.mockk.mockk
|
|
24
|
+
import io.mockk.mockkStatic
|
|
25
|
+
import io.mockk.runs
|
|
17
26
|
import io.mockk.slot
|
|
18
27
|
import io.mockk.verify
|
|
28
|
+
import org.junit.Assert.assertEquals
|
|
19
29
|
import org.junit.Assert.assertTrue
|
|
20
30
|
import org.junit.Before
|
|
21
31
|
import org.junit.Test
|
|
@@ -24,10 +34,13 @@ class RNIapModuleTest {
|
|
|
24
34
|
|
|
25
35
|
@MockK
|
|
26
36
|
lateinit var context: ReactApplicationContext
|
|
37
|
+
|
|
27
38
|
@MockK
|
|
28
39
|
lateinit var builder: BillingClient.Builder
|
|
40
|
+
|
|
29
41
|
@MockK
|
|
30
42
|
lateinit var billingClient: BillingClient
|
|
43
|
+
|
|
31
44
|
@MockK
|
|
32
45
|
lateinit var availability: GoogleApiAvailability
|
|
33
46
|
|
|
@@ -43,9 +56,13 @@ class RNIapModuleTest {
|
|
|
43
56
|
|
|
44
57
|
@Test
|
|
45
58
|
fun `initConnection Already connected should resolve to true`() {
|
|
59
|
+
every { availability.isGooglePlayServicesAvailable(any()) } returns ConnectionResult.SUCCESS
|
|
60
|
+
module.initConnection(mockk())
|
|
61
|
+
|
|
46
62
|
every { billingClient.isReady } returns true
|
|
47
|
-
val promise = mockk<Promise>(relaxed = true)
|
|
48
63
|
|
|
64
|
+
val promise = mockk<Promise>(relaxed = true)
|
|
65
|
+
// Already connected
|
|
49
66
|
module.initConnection(promise)
|
|
50
67
|
verify(exactly = 0) { promise.reject(any(), any<String>()) }
|
|
51
68
|
verify { promise.resolve(true) }
|
|
@@ -67,7 +84,10 @@ class RNIapModuleTest {
|
|
|
67
84
|
every { billingClient.isReady } returns false
|
|
68
85
|
val listener = slot<BillingClientStateListener>()
|
|
69
86
|
every { billingClient.startConnection(capture(listener)) } answers {
|
|
70
|
-
listener.captured.onBillingSetupFinished(
|
|
87
|
+
listener.captured.onBillingSetupFinished(
|
|
88
|
+
BillingResult.newBuilder().setResponseCode(BillingClient.BillingResponseCode.OK)
|
|
89
|
+
.build()
|
|
90
|
+
)
|
|
71
91
|
}
|
|
72
92
|
every { availability.isGooglePlayServicesAvailable(any()) } returns ConnectionResult.SUCCESS
|
|
73
93
|
val promise = mockk<Promise>(relaxed = true)
|
|
@@ -82,7 +102,10 @@ class RNIapModuleTest {
|
|
|
82
102
|
every { billingClient.isReady } returns false
|
|
83
103
|
val listener = slot<BillingClientStateListener>()
|
|
84
104
|
every { billingClient.startConnection(capture(listener)) } answers {
|
|
85
|
-
listener.captured.onBillingSetupFinished(
|
|
105
|
+
listener.captured.onBillingSetupFinished(
|
|
106
|
+
BillingResult.newBuilder().setResponseCode(BillingClient.BillingResponseCode.ERROR)
|
|
107
|
+
.build()
|
|
108
|
+
)
|
|
86
109
|
}
|
|
87
110
|
every { availability.isGooglePlayServicesAvailable(any()) } returns ConnectionResult.SUCCESS
|
|
88
111
|
val promise = mockk<Promise>(relaxed = true)
|
|
@@ -94,8 +117,10 @@ class RNIapModuleTest {
|
|
|
94
117
|
|
|
95
118
|
@Test
|
|
96
119
|
fun `endConnection resolves`() {
|
|
120
|
+
every { availability.isGooglePlayServicesAvailable(any()) } returns ConnectionResult.SUCCESS
|
|
97
121
|
val promise = mockk<Promise>(relaxed = true)
|
|
98
122
|
|
|
123
|
+
module.initConnection(mockk())
|
|
99
124
|
module.endConnection(promise)
|
|
100
125
|
|
|
101
126
|
verify { billingClient.endConnection() }
|
|
@@ -105,12 +130,14 @@ class RNIapModuleTest {
|
|
|
105
130
|
|
|
106
131
|
@Test
|
|
107
132
|
fun `flushFailedPurchasesCachedAsPending resolves to false if no pending purchases`() {
|
|
133
|
+
every { availability.isGooglePlayServicesAvailable(any()) } returns ConnectionResult.SUCCESS
|
|
108
134
|
every { billingClient.isReady } returns true
|
|
109
135
|
val promise = mockk<Promise>(relaxed = true)
|
|
110
136
|
val listener = slot<PurchasesResponseListener>()
|
|
111
137
|
every { billingClient.queryPurchasesAsync(any(), capture(listener)) } answers {
|
|
112
138
|
listener.captured.onQueryPurchasesResponse(BillingResult.newBuilder().build(), listOf())
|
|
113
139
|
}
|
|
140
|
+
module.initConnection(mockk())
|
|
114
141
|
module.flushFailedPurchasesCachedAsPending(promise)
|
|
115
142
|
|
|
116
143
|
verify(exactly = 0) { promise.reject(any(), any<String>()) }
|
|
@@ -119,6 +146,7 @@ class RNIapModuleTest {
|
|
|
119
146
|
|
|
120
147
|
@Test
|
|
121
148
|
fun `flushFailedPurchasesCachedAsPending resolves to true if pending purchases`() {
|
|
149
|
+
every { availability.isGooglePlayServicesAvailable(any()) } returns ConnectionResult.SUCCESS
|
|
122
150
|
every { billingClient.isReady } returns true
|
|
123
151
|
val promise = mockk<Promise>(relaxed = true)
|
|
124
152
|
val listener = slot<PurchasesResponseListener>()
|
|
@@ -137,9 +165,13 @@ class RNIapModuleTest {
|
|
|
137
165
|
}
|
|
138
166
|
val consumeListener = slot<ConsumeResponseListener>()
|
|
139
167
|
every { billingClient.consumeAsync(any(), capture(consumeListener)) } answers {
|
|
140
|
-
consumeListener.captured.onConsumeResponse(
|
|
168
|
+
consumeListener.captured.onConsumeResponse(
|
|
169
|
+
BillingResult.newBuilder()
|
|
170
|
+
.setResponseCode(BillingClient.BillingResponseCode.ITEM_NOT_OWNED).build(),
|
|
171
|
+
""
|
|
172
|
+
)
|
|
141
173
|
}
|
|
142
|
-
|
|
174
|
+
module.initConnection(mockk())
|
|
143
175
|
module.flushFailedPurchasesCachedAsPending(promise)
|
|
144
176
|
|
|
145
177
|
verify(exactly = 0) { promise.reject(any(), any<String>()) }
|
|
@@ -151,12 +183,20 @@ class RNIapModuleTest {
|
|
|
151
183
|
every { availability.isGooglePlayServicesAvailable(any()) } returns ConnectionResult.SUCCESS
|
|
152
184
|
val promise = mockk<Promise>(relaxed = true)
|
|
153
185
|
var isCallbackCalled = false
|
|
154
|
-
val callback = {
|
|
186
|
+
val callback = { _: BillingClient ->
|
|
155
187
|
isCallbackCalled = true
|
|
156
188
|
promise.resolve(true)
|
|
157
189
|
}
|
|
158
190
|
|
|
159
|
-
every { billingClient.isReady } returns
|
|
191
|
+
every { billingClient.isReady } returns true
|
|
192
|
+
val listener = slot<BillingClientStateListener>()
|
|
193
|
+
every { billingClient.startConnection(capture(listener)) } answers {
|
|
194
|
+
listener.captured.onBillingSetupFinished(
|
|
195
|
+
BillingResult.newBuilder().setResponseCode(BillingClient.BillingResponseCode.OK)
|
|
196
|
+
.build()
|
|
197
|
+
)
|
|
198
|
+
}
|
|
199
|
+
|
|
160
200
|
module.ensureConnection(promise, callback)
|
|
161
201
|
verify { promise.resolve(true) } // at least one pending transactions
|
|
162
202
|
assertTrue("Should call callback", isCallbackCalled)
|
|
@@ -164,6 +204,57 @@ class RNIapModuleTest {
|
|
|
164
204
|
|
|
165
205
|
@Test
|
|
166
206
|
fun getItemsByType() {
|
|
207
|
+
every { availability.isGooglePlayServicesAvailable(any()) } returns ConnectionResult.SUCCESS
|
|
208
|
+
every { billingClient.isReady } returns true
|
|
209
|
+
val promise = mockk<Promise>(relaxed = true)
|
|
210
|
+
val listener = slot<SkuDetailsResponseListener>()
|
|
211
|
+
every { billingClient.querySkuDetailsAsync(any(), capture(listener)) } answers {
|
|
212
|
+
listener.captured.onSkuDetailsResponse(
|
|
213
|
+
BillingResult.newBuilder().build(),
|
|
214
|
+
listOf(
|
|
215
|
+
mockk {
|
|
216
|
+
every { sku } returns "sku1"
|
|
217
|
+
every { introductoryPriceAmountMicros } returns 0
|
|
218
|
+
every { priceAmountMicros } returns 1
|
|
219
|
+
every { priceCurrencyCode } returns "USD"
|
|
220
|
+
every { type } returns "sub"
|
|
221
|
+
every { price } returns "$10.0"
|
|
222
|
+
every { title } returns "My product"
|
|
223
|
+
every { description } returns "My desc"
|
|
224
|
+
every { introductoryPrice } returns "$5.0"
|
|
225
|
+
every { zzc() } returns "com.mypackage"
|
|
226
|
+
every { originalPrice } returns "$13.0"
|
|
227
|
+
every { subscriptionPeriod } returns "3 months"
|
|
228
|
+
every { freeTrialPeriod } returns "1 week"
|
|
229
|
+
every { introductoryPriceCycles } returns 1
|
|
230
|
+
every { introductoryPricePeriod } returns "1"
|
|
231
|
+
every { iconUrl } returns "http://myicon.com/icon"
|
|
232
|
+
every { originalJson } returns "{}"
|
|
233
|
+
every { originalPriceAmountMicros } returns 2
|
|
234
|
+
}
|
|
235
|
+
)
|
|
236
|
+
)
|
|
237
|
+
}
|
|
238
|
+
val skus = mockk<ReadableArray>() {
|
|
239
|
+
every { size() } returns 1
|
|
240
|
+
every { getString(0) } returns "sku0"
|
|
241
|
+
every { getType(0) } returns ReadableType.String
|
|
242
|
+
}
|
|
243
|
+
mockkStatic(Arguments::class)
|
|
244
|
+
|
|
245
|
+
val itemsMap = mockk<WritableMap>()
|
|
246
|
+
val itemsArr = mockk<WritableArray>()
|
|
247
|
+
every { Arguments.createMap() } returns itemsMap
|
|
248
|
+
every { Arguments.createArray() } returns itemsArr
|
|
249
|
+
every { itemsMap.putString(any(), any()) } just runs
|
|
250
|
+
var itemsSize = 0
|
|
251
|
+
every { itemsArr.pushMap(any()) } answers {
|
|
252
|
+
itemsSize++
|
|
253
|
+
}
|
|
254
|
+
module.initConnection(mockk())
|
|
255
|
+
module.getItemsByType("subs", skus, promise)
|
|
256
|
+
verify { promise.resolve(any()) }
|
|
257
|
+
assertEquals(itemsSize, 1)
|
|
167
258
|
}
|
|
168
259
|
|
|
169
260
|
@Test
|
package/ios/RNIapIos.m
CHANGED
|
@@ -18,6 +18,7 @@ RCT_EXTERN_METHOD(getAvailableItems:
|
|
|
18
18
|
reject:(RCTPromiseRejectBlock)reject)
|
|
19
19
|
RCT_EXTERN_METHOD(buyProduct:
|
|
20
20
|
(NSString*)sku
|
|
21
|
+
appAccountToken:(NSString*)appAccountToken
|
|
21
22
|
andDangerouslyFinishTransactionAutomatically:(BOOL)andDangerouslyFinishTransactionAutomatically
|
|
22
23
|
resolve:(RCTPromiseResolveBlock)resolve
|
|
23
24
|
reject:(RCTPromiseRejectBlock)reject)
|
package/ios/RNIapIos.swift
CHANGED
|
@@ -178,6 +178,7 @@ class RNIapIos: RCTEventEmitter, SKRequestDelegate, SKPaymentTransactionObserver
|
|
|
178
178
|
|
|
179
179
|
@objc public func buyProduct(
|
|
180
180
|
_ sku:String,
|
|
181
|
+
appAccountToken:String,
|
|
181
182
|
andDangerouslyFinishTransactionAutomatically: Bool,
|
|
182
183
|
resolve: @escaping RCTPromiseResolveBlock = { _ in },
|
|
183
184
|
reject: @escaping RCTPromiseRejectBlock = { _, _, _ in }
|
|
@@ -197,6 +198,7 @@ class RNIapIos: RCTEventEmitter, SKRequestDelegate, SKPaymentTransactionObserver
|
|
|
197
198
|
addPromise(forKey: prod.productIdentifier, resolve: resolve, reject: reject)
|
|
198
199
|
|
|
199
200
|
let payment = SKMutablePayment(product: prod)
|
|
201
|
+
payment.applicationUsername = appAccountToken
|
|
200
202
|
SKPaymentQueue.default().add(payment)
|
|
201
203
|
} else{
|
|
202
204
|
if hasListeners {
|
|
@@ -341,7 +343,7 @@ class RNIapIos: RCTEventEmitter, SKRequestDelegate, SKPaymentTransactionObserver
|
|
|
341
343
|
reject: @escaping RCTPromiseRejectBlock = { _, _, _ in }
|
|
342
344
|
) {
|
|
343
345
|
print("\n\n\n *** get promoted product. \n\n.")
|
|
344
|
-
resolve(promotedProduct )
|
|
346
|
+
resolve((promotedProduct != nil) ? getProductObject(promotedProduct!) : nil)
|
|
345
347
|
}
|
|
346
348
|
|
|
347
349
|
@objc public func buyPromotedProduct(
|
package/ios/RNIapQueue.swift
CHANGED
|
@@ -10,12 +10,13 @@ import StoreKit
|
|
|
10
10
|
|
|
11
11
|
// Temporarily stores payment information since it is sent by the OS before RN instantiates the RNModule
|
|
12
12
|
@objc(RNIapQueue)
|
|
13
|
-
class RNIapQueue: NSObject, SKPaymentTransactionObserver {
|
|
14
|
-
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
|
|
13
|
+
public class RNIapQueue: NSObject, SKPaymentTransactionObserver {
|
|
14
|
+
public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
|
|
15
15
|
//No-op
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
@objc
|
|
19
|
+
public static let shared = RNIapQueue()
|
|
19
20
|
|
|
20
21
|
var queue: SKPaymentQueue? = nil;
|
|
21
22
|
var payment: SKPayment? = nil;
|
package/package.json
CHANGED
package/src/hooks/useIAP.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ declare type IAP_STATUS = {
|
|
|
9
9
|
availablePurchases: Purchase[];
|
|
10
10
|
currentPurchase?: Purchase;
|
|
11
11
|
currentPurchaseError?: PurchaseError;
|
|
12
|
+
initConnectionError?: Error;
|
|
12
13
|
finishTransaction: (purchase: Purchase, isConsumable?: boolean, developerPayloadAndroid?: string) => Promise<string | void>;
|
|
13
14
|
getAvailablePurchases: () => Promise<void>;
|
|
14
15
|
getPurchaseHistories: () => Promise<void>;
|
package/src/hooks/useIAP.js
CHANGED
|
@@ -39,7 +39,7 @@ import { useCallback } from 'react';
|
|
|
39
39
|
import { useIAPContext } from './withIAPContext';
|
|
40
40
|
export function useIAP() {
|
|
41
41
|
var _this = this;
|
|
42
|
-
var _a = useIAPContext(), connected = _a.connected, products = _a.products, promotedProductsIOS = _a.promotedProductsIOS, subscriptions = _a.subscriptions, purchaseHistories = _a.purchaseHistories, availablePurchases = _a.availablePurchases, currentPurchase = _a.currentPurchase, currentPurchaseError = _a.currentPurchaseError, setProducts = _a.setProducts, setSubscriptions = _a.setSubscriptions, setAvailablePurchases = _a.setAvailablePurchases, setPurchaseHistories = _a.setPurchaseHistories, setCurrentPurchase = _a.setCurrentPurchase, setCurrentPurchaseError = _a.setCurrentPurchaseError;
|
|
42
|
+
var _a = useIAPContext(), connected = _a.connected, products = _a.products, promotedProductsIOS = _a.promotedProductsIOS, subscriptions = _a.subscriptions, purchaseHistories = _a.purchaseHistories, availablePurchases = _a.availablePurchases, currentPurchase = _a.currentPurchase, currentPurchaseError = _a.currentPurchaseError, initConnectionError = _a.initConnectionError, setProducts = _a.setProducts, setSubscriptions = _a.setSubscriptions, setAvailablePurchases = _a.setAvailablePurchases, setPurchaseHistories = _a.setPurchaseHistories, setCurrentPurchase = _a.setCurrentPurchase, setCurrentPurchaseError = _a.setCurrentPurchaseError;
|
|
43
43
|
var getProducts = useCallback(function (skus) { return __awaiter(_this, void 0, void 0, function () {
|
|
44
44
|
var _a;
|
|
45
45
|
return __generator(this, function (_b) {
|
|
@@ -129,6 +129,7 @@ export function useIAP() {
|
|
|
129
129
|
availablePurchases: availablePurchases,
|
|
130
130
|
currentPurchase: currentPurchase,
|
|
131
131
|
currentPurchaseError: currentPurchaseError,
|
|
132
|
+
initConnectionError: initConnectionError,
|
|
132
133
|
finishTransaction: finishTransaction,
|
|
133
134
|
getProducts: getProducts,
|
|
134
135
|
getSubscriptions: getSubscriptions,
|
|
@@ -9,6 +9,7 @@ declare type IAPContextType = {
|
|
|
9
9
|
availablePurchases: Purchase[];
|
|
10
10
|
currentPurchase?: Purchase;
|
|
11
11
|
currentPurchaseError?: PurchaseError;
|
|
12
|
+
initConnectionError?: Error;
|
|
12
13
|
setProducts: (products: Product[]) => void;
|
|
13
14
|
setSubscriptions: (subscriptions: Subscription[]) => void;
|
|
14
15
|
setPurchaseHistories: (purchaseHistories: Purchase[]) => void;
|
|
@@ -68,6 +68,7 @@ export function withIAPContext(Component) {
|
|
|
68
68
|
var _f = useState([]), availablePurchases = _f[0], setAvailablePurchases = _f[1];
|
|
69
69
|
var _g = useState(), currentPurchase = _g[0], setCurrentPurchase = _g[1];
|
|
70
70
|
var _h = useState(), currentPurchaseError = _h[0], setCurrentPurchaseError = _h[1];
|
|
71
|
+
var _j = useState(), initConnectionError = _j[0], setInitConnectionError = _j[1];
|
|
71
72
|
var context = useMemo(function () { return ({
|
|
72
73
|
connected: connected,
|
|
73
74
|
products: products,
|
|
@@ -77,6 +78,7 @@ export function withIAPContext(Component) {
|
|
|
77
78
|
availablePurchases: availablePurchases,
|
|
78
79
|
currentPurchase: currentPurchase,
|
|
79
80
|
currentPurchaseError: currentPurchaseError,
|
|
81
|
+
initConnectionError: initConnectionError,
|
|
80
82
|
setProducts: setProducts,
|
|
81
83
|
setSubscriptions: setSubscriptions,
|
|
82
84
|
setPurchaseHistories: setPurchaseHistories,
|
|
@@ -92,6 +94,7 @@ export function withIAPContext(Component) {
|
|
|
92
94
|
availablePurchases,
|
|
93
95
|
currentPurchase,
|
|
94
96
|
currentPurchaseError,
|
|
97
|
+
initConnectionError,
|
|
95
98
|
setProducts,
|
|
96
99
|
setSubscriptions,
|
|
97
100
|
setPurchaseHistories,
|
|
@@ -100,7 +103,12 @@ export function withIAPContext(Component) {
|
|
|
100
103
|
setCurrentPurchaseError,
|
|
101
104
|
]);
|
|
102
105
|
useEffect(function () {
|
|
103
|
-
initConnection()
|
|
106
|
+
initConnection()
|
|
107
|
+
.then(function (value) {
|
|
108
|
+
setInitConnectionError(undefined);
|
|
109
|
+
setConnected(value);
|
|
110
|
+
})
|
|
111
|
+
.catch(setInitConnectionError);
|
|
104
112
|
}, []);
|
|
105
113
|
useEffect(function () {
|
|
106
114
|
if (!connected) {
|
package/src/iap.d.ts
CHANGED
|
@@ -44,12 +44,13 @@ export declare const getAvailablePurchases: () => Promise<(InAppPurchase | Subsc
|
|
|
44
44
|
/**
|
|
45
45
|
* Request a purchase for product. This will be received in `PurchaseUpdatedListener`.
|
|
46
46
|
* @param {string} sku The product's sku/ID
|
|
47
|
+
* @param {string} [appAccountToken] The purchaser's user ID
|
|
47
48
|
* @param {boolean} [andDangerouslyFinishTransactionAutomaticallyIOS] You should set this to false and call finishTransaction manually when you have delivered the purchased goods to the user. It defaults to true to provide backwards compatibility. Will default to false in version 4.0.0.
|
|
48
49
|
* @param {string} [obfuscatedAccountIdAndroid] Specifies an optional obfuscated string that is uniquely associated with the user's account in your app.
|
|
49
50
|
* @param {string} [obfuscatedProfileIdAndroid] Specifies an optional obfuscated string that is uniquely associated with the user's profile in your app.
|
|
50
51
|
* @returns {Promise<InAppPurchase>}
|
|
51
52
|
*/
|
|
52
|
-
export declare const requestPurchase: (sku: string, andDangerouslyFinishTransactionAutomaticallyIOS?: boolean, obfuscatedAccountIdAndroid?: string | undefined, obfuscatedProfileIdAndroid?: string | undefined) => Promise<InAppPurchase>;
|
|
53
|
+
export declare const requestPurchase: (sku: string, appAccountToken: string, andDangerouslyFinishTransactionAutomaticallyIOS?: boolean, obfuscatedAccountIdAndroid?: string | undefined, obfuscatedProfileIdAndroid?: string | undefined) => Promise<InAppPurchase>;
|
|
53
54
|
/**
|
|
54
55
|
* Request a purchase for product. This will be received in `PurchaseUpdatedListener`.
|
|
55
56
|
* @param {string} [sku] The product's sku/ID
|
package/src/iap.js
CHANGED
|
@@ -135,13 +135,13 @@ var fillProductsAdditionalData = function (products) { return __awaiter(void 0,
|
|
|
135
135
|
export var getProducts = function (skus) {
|
|
136
136
|
return (Platform.select({
|
|
137
137
|
ios: function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
138
|
-
var items;
|
|
139
138
|
return __generator(this, function (_a) {
|
|
140
139
|
switch (_a.label) {
|
|
141
|
-
case 0: return [4 /*yield*/, getIosModule()
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
140
|
+
case 0: return [4 /*yield*/, getIosModule()
|
|
141
|
+
.getItems(skus)
|
|
142
|
+
.filter(function (item) { return skus.includes(item.productId); })
|
|
143
|
+
.filter(function (item) { return item.type === 'iap'; })];
|
|
144
|
+
case 1: return [2 /*return*/, _a.sent()];
|
|
145
145
|
}
|
|
146
146
|
});
|
|
147
147
|
}); },
|
|
@@ -166,15 +166,13 @@ export var getProducts = function (skus) {
|
|
|
166
166
|
export var getSubscriptions = function (skus) {
|
|
167
167
|
return (Platform.select({
|
|
168
168
|
ios: function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
169
|
-
var items;
|
|
170
169
|
return __generator(this, function (_a) {
|
|
171
170
|
switch (_a.label) {
|
|
172
|
-
case 0: return [4 /*yield*/, getIosModule()
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
})];
|
|
171
|
+
case 0: return [4 /*yield*/, getIosModule()
|
|
172
|
+
.getItems(skus)
|
|
173
|
+
.filter(function (item) { return skus.includes(item.productId); })
|
|
174
|
+
.filter(function (item) { return item.type === 'subs'; })];
|
|
175
|
+
case 1: return [2 /*return*/, _a.sent()];
|
|
178
176
|
}
|
|
179
177
|
});
|
|
180
178
|
}); },
|
|
@@ -256,12 +254,13 @@ export var getAvailablePurchases = function () {
|
|
|
256
254
|
/**
|
|
257
255
|
* Request a purchase for product. This will be received in `PurchaseUpdatedListener`.
|
|
258
256
|
* @param {string} sku The product's sku/ID
|
|
257
|
+
* @param {string} [appAccountToken] The purchaser's user ID
|
|
259
258
|
* @param {boolean} [andDangerouslyFinishTransactionAutomaticallyIOS] You should set this to false and call finishTransaction manually when you have delivered the purchased goods to the user. It defaults to true to provide backwards compatibility. Will default to false in version 4.0.0.
|
|
260
259
|
* @param {string} [obfuscatedAccountIdAndroid] Specifies an optional obfuscated string that is uniquely associated with the user's account in your app.
|
|
261
260
|
* @param {string} [obfuscatedProfileIdAndroid] Specifies an optional obfuscated string that is uniquely associated with the user's profile in your app.
|
|
262
261
|
* @returns {Promise<InAppPurchase>}
|
|
263
262
|
*/
|
|
264
|
-
export var requestPurchase = function (sku, andDangerouslyFinishTransactionAutomaticallyIOS, obfuscatedAccountIdAndroid, obfuscatedProfileIdAndroid) {
|
|
263
|
+
export var requestPurchase = function (sku, appAccountToken, andDangerouslyFinishTransactionAutomaticallyIOS, obfuscatedAccountIdAndroid, obfuscatedProfileIdAndroid) {
|
|
265
264
|
if (andDangerouslyFinishTransactionAutomaticallyIOS === void 0) { andDangerouslyFinishTransactionAutomaticallyIOS = false; }
|
|
266
265
|
if (obfuscatedAccountIdAndroid === void 0) { obfuscatedAccountIdAndroid = undefined; }
|
|
267
266
|
if (obfuscatedProfileIdAndroid === void 0) { obfuscatedProfileIdAndroid = undefined; }
|
|
@@ -274,7 +273,7 @@ export var requestPurchase = function (sku, andDangerouslyFinishTransactionAutom
|
|
|
274
273
|
// eslint-disable-next-line max-len
|
|
275
274
|
'You are dangerously allowing react-native-iap to finish your transaction automatically. You should set andDangerouslyFinishTransactionAutomatically to false when calling requestPurchase and call finishTransaction manually when you have delivered the purchased goods to the user. It defaults to true to provide backwards compatibility. Will default to false in version 4.0.0.');
|
|
276
275
|
}
|
|
277
|
-
return [2 /*return*/, getIosModule().buyProduct(sku, andDangerouslyFinishTransactionAutomaticallyIOS)];
|
|
276
|
+
return [2 /*return*/, getIosModule().buyProduct(sku, appAccountToken, andDangerouslyFinishTransactionAutomaticallyIOS)];
|
|
278
277
|
});
|
|
279
278
|
}); },
|
|
280
279
|
android: function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
@@ -400,10 +399,19 @@ export var acknowledgePurchaseAndroid = function (token, developerPayload) {
|
|
|
400
399
|
* @param {string} sku The product's SKU (on Android)
|
|
401
400
|
* @returns {Promise<void>}
|
|
402
401
|
*/
|
|
403
|
-
export var deepLinkToSubscriptionsAndroid = function (sku) {
|
|
404
|
-
|
|
405
|
-
return
|
|
406
|
-
|
|
402
|
+
export var deepLinkToSubscriptionsAndroid = function (sku) { return __awaiter(void 0, void 0, void 0, function () {
|
|
403
|
+
var _a, _b, _c;
|
|
404
|
+
return __generator(this, function (_d) {
|
|
405
|
+
switch (_d.label) {
|
|
406
|
+
case 0:
|
|
407
|
+
checkNativeAndroidAvailable();
|
|
408
|
+
_b = (_a = Linking).openURL;
|
|
409
|
+
_c = "https://play.google.com/store/account/subscriptions?package=".concat;
|
|
410
|
+
return [4 /*yield*/, RNIapModule.getPackageName()];
|
|
411
|
+
case 1: return [2 /*return*/, _b.apply(_a, [_c.apply("https://play.google.com/store/account/subscriptions?package=", [_d.sent(), "&sku="]).concat(sku)])];
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
}); };
|
|
407
415
|
/**
|
|
408
416
|
* Should Add Store Payment (iOS only)
|
|
409
417
|
* Indicates the the App Store purchase should continue from the app instead of the App Store.
|
package/src/types/index.d.ts
CHANGED
|
@@ -35,6 +35,8 @@ export declare enum InstallSourceAndroid {
|
|
|
35
35
|
AMAZON = 2
|
|
36
36
|
}
|
|
37
37
|
export interface ProductCommon {
|
|
38
|
+
type: 'subs' | 'sub' | 'inapp' | 'iap';
|
|
39
|
+
productId: string;
|
|
38
40
|
title: string;
|
|
39
41
|
description: string;
|
|
40
42
|
price: string;
|
|
@@ -96,11 +98,9 @@ export interface Discount {
|
|
|
96
98
|
}
|
|
97
99
|
export interface Product extends ProductCommon {
|
|
98
100
|
type: 'inapp' | 'iap';
|
|
99
|
-
productId: string;
|
|
100
101
|
}
|
|
101
102
|
export interface Subscription extends ProductCommon {
|
|
102
103
|
type: 'subs' | 'sub';
|
|
103
|
-
productId: string;
|
|
104
104
|
discounts?: Discount[];
|
|
105
105
|
introductoryPrice?: string;
|
|
106
106
|
introductoryPriceAsAmountIOS?: string;
|