react-native-iap 8.1.1 → 8.2.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/android/build.gradle +5 -0
- package/android/src/play/java/com/dooboolab/RNIap/PlayUtils.kt +1 -1
- package/android/src/play/java/com/dooboolab/RNIap/RNIapModule.kt +373 -411
- package/android/src/testPlay/java/com/dooboolab/RNIap/RNIapModuleTest.kt +144 -0
- package/package.json +1 -1
package/.yarn/install-state.gz
CHANGED
|
Binary file
|
package/android/build.gradle
CHANGED
|
@@ -50,6 +50,9 @@ android {
|
|
|
50
50
|
dimension "store"
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
|
+
testOptions {
|
|
54
|
+
unitTests.returnDefaultValues = true
|
|
55
|
+
}
|
|
53
56
|
}
|
|
54
57
|
|
|
55
58
|
repositories {
|
|
@@ -63,6 +66,8 @@ repositories {
|
|
|
63
66
|
|
|
64
67
|
dependencies {
|
|
65
68
|
implementation 'com.facebook.react:react-native:+'
|
|
69
|
+
testImplementation 'junit:junit:4.12'
|
|
70
|
+
testImplementation "io.mockk:mockk:1.12.4"
|
|
66
71
|
playImplementation 'com.android.billingclient:billing:4.0.0'
|
|
67
72
|
def playServicesVersion = safeExtGet('playServicesVersion', DEFAULT_PLAY_SERVICES_VERSION)
|
|
68
73
|
playImplementation "com.google.android.gms:play-services-base:$playServicesVersion"
|
|
@@ -49,7 +49,7 @@ class PlayUtils {
|
|
|
49
49
|
}
|
|
50
50
|
BillingClient.BillingResponseCode.ERROR -> {
|
|
51
51
|
errorData[0] = DoobooUtils.E_UNKNOWN
|
|
52
|
-
errorData[1] = "An unknown or unexpected error has
|
|
52
|
+
errorData[1] = "An unknown or unexpected error has occurred. Please try again later."
|
|
53
53
|
}
|
|
54
54
|
BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED -> {
|
|
55
55
|
errorData[0] = DoobooUtils.E_ALREADY_OWNED
|
|
@@ -17,6 +17,7 @@ import com.facebook.react.bridge.Arguments
|
|
|
17
17
|
import com.facebook.react.bridge.LifecycleEventListener
|
|
18
18
|
import com.facebook.react.bridge.ObjectAlreadyConsumedException
|
|
19
19
|
import com.facebook.react.bridge.Promise
|
|
20
|
+
import com.facebook.react.bridge.PromiseImpl
|
|
20
21
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
21
22
|
import com.facebook.react.bridge.ReactContext
|
|
22
23
|
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
@@ -28,37 +29,53 @@ import com.facebook.react.bridge.WritableNativeMap
|
|
|
28
29
|
import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter
|
|
29
30
|
import com.google.android.gms.common.ConnectionResult
|
|
30
31
|
import com.google.android.gms.common.GoogleApiAvailability
|
|
31
|
-
import java.lang.Exception
|
|
32
32
|
import java.math.BigDecimal
|
|
33
33
|
import java.util.ArrayList
|
|
34
34
|
|
|
35
|
-
class RNIapModule(
|
|
35
|
+
class RNIapModule(
|
|
36
|
+
private val reactContext: ReactApplicationContext,
|
|
37
|
+
private val builder: BillingClient.Builder = BillingClient.newBuilder(reactContext).enablePendingPurchases(),
|
|
38
|
+
private val googleApiAvailability: GoogleApiAvailability = GoogleApiAvailability.getInstance()
|
|
39
|
+
) :
|
|
36
40
|
ReactContextBaseJavaModule(reactContext),
|
|
37
41
|
PurchasesUpdatedListener {
|
|
38
|
-
|
|
39
|
-
private
|
|
40
|
-
private
|
|
41
|
-
private val skus: MutableMap<String, SkuDetails>
|
|
42
|
+
|
|
43
|
+
private var billingClient: BillingClient = builder.setListener(this).build()
|
|
44
|
+
private val skus: MutableMap<String, SkuDetails> = mutableMapOf()
|
|
42
45
|
override fun getName(): String {
|
|
43
46
|
return "RNIapModule"
|
|
44
47
|
}
|
|
45
48
|
|
|
46
|
-
private
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
private fun ensureConnection(promise: Promise, callback: EnsureConnectionCallback) {
|
|
51
|
-
val billingClient = billingClientCache
|
|
52
|
-
if (billingClient != null && billingClient.isReady) {
|
|
53
|
-
callback.run(billingClient)
|
|
49
|
+
private fun ensureConnection(promise: Promise, callback: () -> Unit) {
|
|
50
|
+
if (billingClient.isReady) {
|
|
51
|
+
callback()
|
|
54
52
|
return
|
|
53
|
+
} else {
|
|
54
|
+
val nested = PromiseImpl(
|
|
55
|
+
{
|
|
56
|
+
if (it.isNotEmpty() && it[0] is Boolean && it[0] as Boolean) {
|
|
57
|
+
callback()
|
|
58
|
+
} else {
|
|
59
|
+
Log.i(TAG, "Incorrect parameter in resolve")
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
if (it.size > 1 && it[0] is String && it[1] is String) {
|
|
64
|
+
promise.reject(
|
|
65
|
+
it[0] as String, it[1] as String
|
|
66
|
+
)
|
|
67
|
+
} else {
|
|
68
|
+
Log.i(TAG, "Incorrect parameters in reject")
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
)
|
|
72
|
+
initConnection(nested)
|
|
55
73
|
}
|
|
56
|
-
promise.reject(DoobooUtils.E_NOT_PREPARED, "Not initialized, Please call initConnection()")
|
|
57
74
|
}
|
|
58
75
|
|
|
59
76
|
@ReactMethod
|
|
60
77
|
fun initConnection(promise: Promise) {
|
|
61
|
-
if (
|
|
78
|
+
if (billingClient.isReady) {
|
|
62
79
|
Log.i(
|
|
63
80
|
TAG,
|
|
64
81
|
"Already initialized, you should only call initConnection() once when your app starts"
|
|
@@ -66,17 +83,17 @@ class RNIapModule(reactContext: ReactApplicationContext) :
|
|
|
66
83
|
promise.resolve(true)
|
|
67
84
|
return
|
|
68
85
|
}
|
|
69
|
-
if (
|
|
86
|
+
if (googleApiAvailability.isGooglePlayServicesAvailable(reactContext)
|
|
70
87
|
!= ConnectionResult.SUCCESS
|
|
71
88
|
) {
|
|
72
89
|
Log.i(TAG, "Google Play Services are not available on this device")
|
|
73
|
-
promise.
|
|
90
|
+
promise.reject(DoobooUtils.E_NOT_PREPARED, "Google Play Services are not available on this device")
|
|
74
91
|
return
|
|
75
92
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
93
|
+
|
|
94
|
+
billingClient = builder.setListener(this).build()
|
|
95
|
+
|
|
96
|
+
billingClient.startConnection(
|
|
80
97
|
object : BillingClientStateListener {
|
|
81
98
|
override fun onBillingSetupFinished(billingResult: BillingResult) {
|
|
82
99
|
val responseCode = billingResult.responseCode
|
|
@@ -93,31 +110,15 @@ class RNIapModule(reactContext: ReactApplicationContext) :
|
|
|
93
110
|
}
|
|
94
111
|
|
|
95
112
|
override fun onBillingServiceDisconnected() {
|
|
96
|
-
|
|
97
|
-
promise.reject("initConnection", "Billing service disconnected")
|
|
98
|
-
} catch (oce: ObjectAlreadyConsumedException) {
|
|
99
|
-
Log.e(TAG, oce.message!!)
|
|
100
|
-
}
|
|
113
|
+
Log.i(TAG, "Billing service disconnected")
|
|
101
114
|
}
|
|
102
115
|
})
|
|
103
116
|
}
|
|
104
117
|
|
|
105
118
|
@ReactMethod
|
|
106
119
|
fun endConnection(promise: Promise) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
billingClientCache!!.endConnection()
|
|
110
|
-
null
|
|
111
|
-
} catch (e: Exception) {
|
|
112
|
-
promise.reject("endConnection", e.message)
|
|
113
|
-
return
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
try {
|
|
117
|
-
promise.resolve(true)
|
|
118
|
-
} catch (oce: ObjectAlreadyConsumedException) {
|
|
119
|
-
Log.e(TAG, oce.message!!)
|
|
120
|
-
}
|
|
120
|
+
billingClient.endConnection()
|
|
121
|
+
promise.resolve(true)
|
|
121
122
|
}
|
|
122
123
|
|
|
123
124
|
private fun consumeItems(
|
|
@@ -127,251 +128,230 @@ class RNIapModule(reactContext: ReactApplicationContext) :
|
|
|
127
128
|
) {
|
|
128
129
|
for (purchase in purchases) {
|
|
129
130
|
ensureConnection(
|
|
130
|
-
promise
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
billingClient.consumeAsync(consumeParams, listener)
|
|
131
|
+
promise
|
|
132
|
+
) {
|
|
133
|
+
val consumeParams =
|
|
134
|
+
ConsumeParams.newBuilder().setPurchaseToken(purchase.purchaseToken)
|
|
135
|
+
.build()
|
|
136
|
+
val listener =
|
|
137
|
+
ConsumeResponseListener { billingResult: BillingResult, outToken: String? ->
|
|
138
|
+
if (billingResult.responseCode != expectedResponseCode) {
|
|
139
|
+
PlayUtils.instance
|
|
140
|
+
.rejectPromiseWithBillingError(
|
|
141
|
+
promise,
|
|
142
|
+
billingResult.responseCode
|
|
143
|
+
)
|
|
144
|
+
return@ConsumeResponseListener
|
|
145
|
+
}
|
|
146
|
+
try {
|
|
147
|
+
promise.resolve(true)
|
|
148
|
+
} catch (oce: ObjectAlreadyConsumedException) {
|
|
149
|
+
promise.reject(oce.message)
|
|
150
|
+
}
|
|
153
151
|
}
|
|
154
|
-
|
|
155
|
-
|
|
152
|
+
billingClient.consumeAsync(consumeParams, listener)
|
|
153
|
+
}
|
|
156
154
|
}
|
|
157
155
|
}
|
|
158
156
|
|
|
159
157
|
@ReactMethod
|
|
160
158
|
fun flushFailedPurchasesCachedAsPending(promise: Promise) {
|
|
161
159
|
ensureConnection(
|
|
162
|
-
promise
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
if (purchase.purchaseState == Purchase.PurchaseState.PENDING) {
|
|
180
|
-
pendingPurchases.add(purchase)
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
if (pendingPurchases.size == 0) {
|
|
184
|
-
promise.resolve(false)
|
|
185
|
-
return@queryPurchasesAsync
|
|
186
|
-
}
|
|
187
|
-
consumeItems(
|
|
188
|
-
pendingPurchases,
|
|
189
|
-
promise,
|
|
190
|
-
BillingClient.BillingResponseCode.ITEM_NOT_OWNED
|
|
191
|
-
)
|
|
192
|
-
}
|
|
160
|
+
promise
|
|
161
|
+
) {
|
|
162
|
+
billingClient.queryPurchasesAsync(
|
|
163
|
+
BillingClient.SkuType.INAPP
|
|
164
|
+
) { _: BillingResult?, list: List<Purchase>? ->
|
|
165
|
+
if (list == null) {
|
|
166
|
+
// No purchases found
|
|
167
|
+
promise.resolve(false)
|
|
168
|
+
return@queryPurchasesAsync
|
|
169
|
+
}
|
|
170
|
+
// we only want to try to consume PENDING items, in order to force cache-refresh
|
|
171
|
+
// for them
|
|
172
|
+
val pendingPurchases = list.filter { it.purchaseState == Purchase.PurchaseState.PENDING }
|
|
173
|
+
|
|
174
|
+
if (pendingPurchases.isEmpty()) {
|
|
175
|
+
promise.resolve(false)
|
|
176
|
+
return@queryPurchasesAsync
|
|
193
177
|
}
|
|
178
|
+
consumeItems(
|
|
179
|
+
pendingPurchases,
|
|
180
|
+
promise,
|
|
181
|
+
BillingClient.BillingResponseCode.ITEM_NOT_OWNED
|
|
182
|
+
)
|
|
194
183
|
}
|
|
195
|
-
|
|
184
|
+
}
|
|
196
185
|
}
|
|
197
186
|
|
|
198
187
|
@ReactMethod
|
|
199
188
|
fun getItemsByType(type: String?, skuArr: ReadableArray, promise: Promise) {
|
|
200
189
|
ensureConnection(
|
|
201
|
-
promise
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
skus.put(sku.getSku(), sku)
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
val items = WritableNativeArray()
|
|
228
|
-
for (skuDetails in skuDetailsList!!) {
|
|
229
|
-
val item = Arguments.createMap()
|
|
230
|
-
item.putString("productId", skuDetails.sku)
|
|
231
|
-
val introductoryPriceMicros = skuDetails.introductoryPriceAmountMicros
|
|
232
|
-
val priceAmountMicros = skuDetails.priceAmountMicros
|
|
233
|
-
// Use valueOf instead of constructors.
|
|
234
|
-
// See:
|
|
235
|
-
// https://www.javaworld.com/article/2073176/caution--double-to-bigdecimal-in-java.html
|
|
236
|
-
val priceAmount = BigDecimal.valueOf(priceAmountMicros)
|
|
237
|
-
val introductoryPriceAmount =
|
|
238
|
-
BigDecimal.valueOf(introductoryPriceMicros)
|
|
239
|
-
val microUnitsDivisor = BigDecimal.valueOf(1000000)
|
|
240
|
-
val price = priceAmount.divide(microUnitsDivisor).toString()
|
|
241
|
-
val introductoryPriceAsAmountAndroid =
|
|
242
|
-
introductoryPriceAmount.divide(microUnitsDivisor).toString()
|
|
243
|
-
item.putString("price", price)
|
|
244
|
-
item.putString("currency", skuDetails.priceCurrencyCode)
|
|
245
|
-
item.putString("type", skuDetails.type)
|
|
246
|
-
item.putString("localizedPrice", skuDetails.price)
|
|
247
|
-
item.putString("title", skuDetails.title)
|
|
248
|
-
item.putString("description", skuDetails.description)
|
|
249
|
-
item.putString("introductoryPrice", skuDetails.introductoryPrice)
|
|
250
|
-
item.putString("typeAndroid", skuDetails.type)
|
|
251
|
-
item.putString("packageNameAndroid", skuDetails.zzc())
|
|
252
|
-
item.putString("originalPriceAndroid", skuDetails.originalPrice)
|
|
253
|
-
item.putString(
|
|
254
|
-
"subscriptionPeriodAndroid",
|
|
255
|
-
skuDetails.subscriptionPeriod
|
|
256
|
-
)
|
|
257
|
-
item.putString("freeTrialPeriodAndroid", skuDetails.freeTrialPeriod)
|
|
258
|
-
item.putString(
|
|
259
|
-
"introductoryPriceCyclesAndroid",
|
|
260
|
-
skuDetails.introductoryPriceCycles.toString()
|
|
261
|
-
)
|
|
262
|
-
item.putString(
|
|
263
|
-
"introductoryPricePeriodAndroid", skuDetails.introductoryPricePeriod
|
|
264
|
-
)
|
|
265
|
-
item.putString(
|
|
266
|
-
"introductoryPriceAsAmountAndroid", introductoryPriceAsAmountAndroid
|
|
267
|
-
)
|
|
268
|
-
item.putString("iconUrl", skuDetails.iconUrl)
|
|
269
|
-
item.putString("originalJson", skuDetails.originalJson)
|
|
270
|
-
val originalPriceAmountMicros =
|
|
271
|
-
BigDecimal.valueOf(skuDetails.originalPriceAmountMicros)
|
|
272
|
-
val originalPrice =
|
|
273
|
-
originalPriceAmountMicros.divide(microUnitsDivisor).toString()
|
|
274
|
-
item.putString("originalPrice", originalPrice)
|
|
275
|
-
items.pushMap(item)
|
|
276
|
-
}
|
|
277
|
-
try {
|
|
278
|
-
promise.resolve(items)
|
|
279
|
-
} catch (oce: ObjectAlreadyConsumedException) {
|
|
280
|
-
Log.e(TAG, oce.message!!)
|
|
281
|
-
}
|
|
190
|
+
promise
|
|
191
|
+
) {
|
|
192
|
+
val skuList = ArrayList<String>()
|
|
193
|
+
for (i in 0 until skuArr.size()) {
|
|
194
|
+
val sku = skuArr.getString(i)
|
|
195
|
+
if (sku is String) {
|
|
196
|
+
skuList.add(sku)
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
val params = SkuDetailsParams.newBuilder()
|
|
200
|
+
params.setSkusList(skuList).setType(type!!)
|
|
201
|
+
billingClient.querySkuDetailsAsync(
|
|
202
|
+
params.build()
|
|
203
|
+
) { billingResult: BillingResult, skuDetailsList: List<SkuDetails>? ->
|
|
204
|
+
Log.d(TAG, "responseCode: " + billingResult.responseCode)
|
|
205
|
+
if (billingResult.responseCode != BillingClient.BillingResponseCode.OK) {
|
|
206
|
+
PlayUtils.instance
|
|
207
|
+
.rejectPromiseWithBillingError(promise, billingResult.responseCode)
|
|
208
|
+
return@querySkuDetailsAsync
|
|
209
|
+
}
|
|
210
|
+
if (skuDetailsList != null) {
|
|
211
|
+
for (sku in skuDetailsList) {
|
|
212
|
+
skus[sku.sku] = sku
|
|
282
213
|
}
|
|
283
214
|
}
|
|
215
|
+
val items = WritableNativeArray()
|
|
216
|
+
for (skuDetails in skuDetailsList!!) {
|
|
217
|
+
val item = Arguments.createMap()
|
|
218
|
+
item.putString("productId", skuDetails.sku)
|
|
219
|
+
val introductoryPriceMicros = skuDetails.introductoryPriceAmountMicros
|
|
220
|
+
val priceAmountMicros = skuDetails.priceAmountMicros
|
|
221
|
+
// Use valueOf instead of constructors.
|
|
222
|
+
// See:
|
|
223
|
+
// https://www.javaworld.com/article/2073176/caution--double-to-bigdecimal-in-java.html
|
|
224
|
+
val priceAmount = BigDecimal.valueOf(priceAmountMicros)
|
|
225
|
+
val introductoryPriceAmount =
|
|
226
|
+
BigDecimal.valueOf(introductoryPriceMicros)
|
|
227
|
+
val microUnitsDivisor = BigDecimal.valueOf(1000000)
|
|
228
|
+
val price = priceAmount.divide(microUnitsDivisor).toString()
|
|
229
|
+
val introductoryPriceAsAmountAndroid =
|
|
230
|
+
introductoryPriceAmount.divide(microUnitsDivisor).toString()
|
|
231
|
+
item.putString("price", price)
|
|
232
|
+
item.putString("currency", skuDetails.priceCurrencyCode)
|
|
233
|
+
item.putString("type", skuDetails.type)
|
|
234
|
+
item.putString("localizedPrice", skuDetails.price)
|
|
235
|
+
item.putString("title", skuDetails.title)
|
|
236
|
+
item.putString("description", skuDetails.description)
|
|
237
|
+
item.putString("introductoryPrice", skuDetails.introductoryPrice)
|
|
238
|
+
item.putString("typeAndroid", skuDetails.type)
|
|
239
|
+
item.putString("packageNameAndroid", skuDetails.zzc())
|
|
240
|
+
item.putString("originalPriceAndroid", skuDetails.originalPrice)
|
|
241
|
+
item.putString(
|
|
242
|
+
"subscriptionPeriodAndroid",
|
|
243
|
+
skuDetails.subscriptionPeriod
|
|
244
|
+
)
|
|
245
|
+
item.putString("freeTrialPeriodAndroid", skuDetails.freeTrialPeriod)
|
|
246
|
+
item.putString(
|
|
247
|
+
"introductoryPriceCyclesAndroid",
|
|
248
|
+
skuDetails.introductoryPriceCycles.toString()
|
|
249
|
+
)
|
|
250
|
+
item.putString(
|
|
251
|
+
"introductoryPricePeriodAndroid", skuDetails.introductoryPricePeriod
|
|
252
|
+
)
|
|
253
|
+
item.putString(
|
|
254
|
+
"introductoryPriceAsAmountAndroid", introductoryPriceAsAmountAndroid
|
|
255
|
+
)
|
|
256
|
+
item.putString("iconUrl", skuDetails.iconUrl)
|
|
257
|
+
item.putString("originalJson", skuDetails.originalJson)
|
|
258
|
+
val originalPriceAmountMicros =
|
|
259
|
+
BigDecimal.valueOf(skuDetails.originalPriceAmountMicros)
|
|
260
|
+
val originalPrice =
|
|
261
|
+
originalPriceAmountMicros.divide(microUnitsDivisor).toString()
|
|
262
|
+
item.putString("originalPrice", originalPrice)
|
|
263
|
+
items.pushMap(item)
|
|
264
|
+
}
|
|
265
|
+
try {
|
|
266
|
+
promise.resolve(items)
|
|
267
|
+
} catch (oce: ObjectAlreadyConsumedException) {
|
|
268
|
+
Log.e(TAG, oce.message!!)
|
|
269
|
+
}
|
|
284
270
|
}
|
|
285
|
-
|
|
271
|
+
}
|
|
286
272
|
}
|
|
287
273
|
|
|
288
274
|
@ReactMethod
|
|
289
275
|
fun getAvailableItemsByType(type: String, promise: Promise) {
|
|
290
276
|
ensureConnection(
|
|
291
|
-
promise
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
item.putBoolean("autoRenewingAndroid", purchase.isAutoRenewing)
|
|
323
|
-
}
|
|
324
|
-
items.pushMap(item)
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
try {
|
|
328
|
-
promise.resolve(items)
|
|
329
|
-
} catch (oce: ObjectAlreadyConsumedException) {
|
|
330
|
-
Log.e(TAG, oce.message!!)
|
|
277
|
+
promise
|
|
278
|
+
) {
|
|
279
|
+
val items = WritableNativeArray()
|
|
280
|
+
billingClient.queryPurchasesAsync(
|
|
281
|
+
if (type == "subs") BillingClient.SkuType.SUBS else BillingClient.SkuType.INAPP
|
|
282
|
+
) { billingResult: BillingResult?, purchases: List<Purchase>? ->
|
|
283
|
+
if (purchases != null) {
|
|
284
|
+
for (i in purchases.indices) {
|
|
285
|
+
val purchase = purchases[i]
|
|
286
|
+
val item = WritableNativeMap()
|
|
287
|
+
item.putString("productId", purchase.skus[0])
|
|
288
|
+
item.putString("transactionId", purchase.orderId)
|
|
289
|
+
item.putDouble("transactionDate", purchase.purchaseTime.toDouble())
|
|
290
|
+
item.putString("transactionReceipt", purchase.originalJson)
|
|
291
|
+
item.putString("orderId", purchase.orderId)
|
|
292
|
+
item.putString("purchaseToken", purchase.purchaseToken)
|
|
293
|
+
item.putString("developerPayloadAndroid", purchase.developerPayload)
|
|
294
|
+
item.putString("signatureAndroid", purchase.signature)
|
|
295
|
+
item.putInt("purchaseStateAndroid", purchase.purchaseState)
|
|
296
|
+
item.putBoolean("isAcknowledgedAndroid", purchase.isAcknowledged)
|
|
297
|
+
item.putString("packageNameAndroid", purchase.packageName)
|
|
298
|
+
item.putString(
|
|
299
|
+
"obfuscatedAccountIdAndroid",
|
|
300
|
+
purchase.accountIdentifiers!!.obfuscatedAccountId
|
|
301
|
+
)
|
|
302
|
+
item.putString(
|
|
303
|
+
"obfuscatedProfileIdAndroid",
|
|
304
|
+
purchase.accountIdentifiers!!.obfuscatedProfileId
|
|
305
|
+
)
|
|
306
|
+
if (type == BillingClient.SkuType.SUBS) {
|
|
307
|
+
item.putBoolean("autoRenewingAndroid", purchase.isAutoRenewing)
|
|
331
308
|
}
|
|
309
|
+
items.pushMap(item)
|
|
332
310
|
}
|
|
333
311
|
}
|
|
312
|
+
try {
|
|
313
|
+
promise.resolve(items)
|
|
314
|
+
} catch (oce: ObjectAlreadyConsumedException) {
|
|
315
|
+
Log.e(TAG, oce.message!!)
|
|
316
|
+
}
|
|
334
317
|
}
|
|
335
|
-
|
|
318
|
+
}
|
|
336
319
|
}
|
|
337
320
|
|
|
338
321
|
@ReactMethod
|
|
339
322
|
fun getPurchaseHistoryByType(type: String, promise: Promise) {
|
|
340
323
|
ensureConnection(
|
|
341
|
-
promise
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
Log.e(TAG, oce.message!!)
|
|
370
|
-
}
|
|
371
|
-
}
|
|
324
|
+
promise
|
|
325
|
+
) {
|
|
326
|
+
billingClient.queryPurchaseHistoryAsync(
|
|
327
|
+
if (type == "subs") BillingClient.SkuType.SUBS else BillingClient.SkuType.INAPP
|
|
328
|
+
) { billingResult, purchaseHistoryRecordList ->
|
|
329
|
+
if (billingResult.responseCode != BillingClient.BillingResponseCode.OK) {
|
|
330
|
+
PlayUtils.instance
|
|
331
|
+
.rejectPromiseWithBillingError(promise, billingResult.responseCode)
|
|
332
|
+
return@queryPurchaseHistoryAsync
|
|
333
|
+
}
|
|
334
|
+
Log.d(TAG, purchaseHistoryRecordList.toString())
|
|
335
|
+
val items = Arguments.createArray()
|
|
336
|
+
for (i in purchaseHistoryRecordList!!.indices) {
|
|
337
|
+
val item = Arguments.createMap()
|
|
338
|
+
val purchase = purchaseHistoryRecordList[i]
|
|
339
|
+
item.putString("productId", purchase.skus[0])
|
|
340
|
+
item.putDouble("transactionDate", purchase.purchaseTime.toDouble())
|
|
341
|
+
item.putString("transactionReceipt", purchase.originalJson)
|
|
342
|
+
item.putString("purchaseToken", purchase.purchaseToken)
|
|
343
|
+
item.putString("dataAndroid", purchase.originalJson)
|
|
344
|
+
item.putString("signatureAndroid", purchase.signature)
|
|
345
|
+
item.putString("developerPayload", purchase.developerPayload)
|
|
346
|
+
items.pushMap(item)
|
|
347
|
+
}
|
|
348
|
+
try {
|
|
349
|
+
promise.resolve(items)
|
|
350
|
+
} catch (oce: ObjectAlreadyConsumedException) {
|
|
351
|
+
Log.e(TAG, oce.message!!)
|
|
372
352
|
}
|
|
373
353
|
}
|
|
374
|
-
|
|
354
|
+
}
|
|
375
355
|
}
|
|
376
356
|
|
|
377
357
|
@ReactMethod
|
|
@@ -390,17 +370,49 @@ class RNIapModule(reactContext: ReactApplicationContext) :
|
|
|
390
370
|
return
|
|
391
371
|
}
|
|
392
372
|
ensureConnection(
|
|
393
|
-
promise
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
373
|
+
promise
|
|
374
|
+
) {
|
|
375
|
+
DoobooUtils.instance.addPromiseForKey(
|
|
376
|
+
PROMISE_BUY_ITEM, promise
|
|
377
|
+
)
|
|
378
|
+
val builder = BillingFlowParams.newBuilder()
|
|
379
|
+
val selectedSku: SkuDetails? = skus[sku]
|
|
380
|
+
if (selectedSku == null) {
|
|
381
|
+
val debugMessage =
|
|
382
|
+
"The sku was not found. Please fetch products first by calling getItems"
|
|
383
|
+
val error = Arguments.createMap()
|
|
384
|
+
error.putString("debugMessage", debugMessage)
|
|
385
|
+
error.putString("code", PROMISE_BUY_ITEM)
|
|
386
|
+
error.putString("message", debugMessage)
|
|
387
|
+
error.putString("productId", sku)
|
|
388
|
+
sendEvent(reactContext, "purchase-error", error)
|
|
389
|
+
promise.reject(PROMISE_BUY_ITEM, debugMessage)
|
|
390
|
+
return@ensureConnection
|
|
391
|
+
}
|
|
392
|
+
builder.setSkuDetails(selectedSku)
|
|
393
|
+
val subscriptionUpdateParamsBuilder = SubscriptionUpdateParams.newBuilder()
|
|
394
|
+
if (purchaseToken != null) {
|
|
395
|
+
subscriptionUpdateParamsBuilder.setOldSkuPurchaseToken(purchaseToken)
|
|
396
|
+
}
|
|
397
|
+
if (obfuscatedAccountId != null) {
|
|
398
|
+
builder.setObfuscatedAccountId(obfuscatedAccountId)
|
|
399
|
+
}
|
|
400
|
+
if (obfuscatedProfileId != null) {
|
|
401
|
+
builder.setObfuscatedProfileId(obfuscatedProfileId)
|
|
402
|
+
}
|
|
403
|
+
if (prorationMode != null && prorationMode != -1) {
|
|
404
|
+
if (prorationMode
|
|
405
|
+
== BillingFlowParams.ProrationMode.IMMEDIATE_AND_CHARGE_PRORATED_PRICE
|
|
406
|
+
) {
|
|
407
|
+
subscriptionUpdateParamsBuilder.setReplaceSkusProrationMode(
|
|
408
|
+
BillingFlowParams.ProrationMode.IMMEDIATE_AND_CHARGE_PRORATED_PRICE
|
|
398
409
|
)
|
|
399
|
-
|
|
400
|
-
var selectedSku: SkuDetails? = skus.get(sku)
|
|
401
|
-
if (selectedSku == null) {
|
|
410
|
+
if (type != BillingClient.SkuType.SUBS) {
|
|
402
411
|
val debugMessage =
|
|
403
|
-
|
|
412
|
+
(
|
|
413
|
+
"IMMEDIATE_AND_CHARGE_PRORATED_PRICE for proration mode only works in" +
|
|
414
|
+
" subscription purchase."
|
|
415
|
+
)
|
|
404
416
|
val error = Arguments.createMap()
|
|
405
417
|
error.putString("debugMessage", debugMessage)
|
|
406
418
|
error.putString("code", PROMISE_BUY_ITEM)
|
|
@@ -408,80 +420,45 @@ class RNIapModule(reactContext: ReactApplicationContext) :
|
|
|
408
420
|
error.putString("productId", sku)
|
|
409
421
|
sendEvent(reactContext, "purchase-error", error)
|
|
410
422
|
promise.reject(PROMISE_BUY_ITEM, debugMessage)
|
|
411
|
-
return
|
|
412
|
-
}
|
|
413
|
-
builder.setSkuDetails(selectedSku)
|
|
414
|
-
val subscriptionUpdateParamsBuilder = SubscriptionUpdateParams.newBuilder()
|
|
415
|
-
if (purchaseToken != null) {
|
|
416
|
-
subscriptionUpdateParamsBuilder.setOldSkuPurchaseToken(purchaseToken)
|
|
417
|
-
}
|
|
418
|
-
if (obfuscatedAccountId != null) {
|
|
419
|
-
builder.setObfuscatedAccountId(obfuscatedAccountId)
|
|
420
|
-
}
|
|
421
|
-
if (obfuscatedProfileId != null) {
|
|
422
|
-
builder.setObfuscatedProfileId(obfuscatedProfileId)
|
|
423
|
-
}
|
|
424
|
-
if (prorationMode != null && prorationMode != -1) {
|
|
425
|
-
if (prorationMode
|
|
426
|
-
== BillingFlowParams.ProrationMode.IMMEDIATE_AND_CHARGE_PRORATED_PRICE
|
|
427
|
-
) {
|
|
428
|
-
subscriptionUpdateParamsBuilder.setReplaceSkusProrationMode(
|
|
429
|
-
BillingFlowParams.ProrationMode.IMMEDIATE_AND_CHARGE_PRORATED_PRICE
|
|
430
|
-
)
|
|
431
|
-
if (type != BillingClient.SkuType.SUBS) {
|
|
432
|
-
val debugMessage =
|
|
433
|
-
(
|
|
434
|
-
"IMMEDIATE_AND_CHARGE_PRORATED_PRICE for proration mode only works in" +
|
|
435
|
-
" subscription purchase."
|
|
436
|
-
)
|
|
437
|
-
val error = Arguments.createMap()
|
|
438
|
-
error.putString("debugMessage", debugMessage)
|
|
439
|
-
error.putString("code", PROMISE_BUY_ITEM)
|
|
440
|
-
error.putString("message", debugMessage)
|
|
441
|
-
error.putString("productId", sku)
|
|
442
|
-
sendEvent(reactContext, "purchase-error", error)
|
|
443
|
-
promise.reject(PROMISE_BUY_ITEM, debugMessage)
|
|
444
|
-
return
|
|
445
|
-
}
|
|
446
|
-
} else if (prorationMode
|
|
447
|
-
== BillingFlowParams.ProrationMode.IMMEDIATE_WITHOUT_PRORATION
|
|
448
|
-
) {
|
|
449
|
-
subscriptionUpdateParamsBuilder.setReplaceSkusProrationMode(
|
|
450
|
-
BillingFlowParams.ProrationMode.IMMEDIATE_WITHOUT_PRORATION
|
|
451
|
-
)
|
|
452
|
-
} else if (prorationMode == BillingFlowParams.ProrationMode.DEFERRED) {
|
|
453
|
-
subscriptionUpdateParamsBuilder.setReplaceSkusProrationMode(
|
|
454
|
-
BillingFlowParams.ProrationMode.DEFERRED
|
|
455
|
-
)
|
|
456
|
-
} else if (prorationMode
|
|
457
|
-
== BillingFlowParams.ProrationMode.IMMEDIATE_WITH_TIME_PRORATION
|
|
458
|
-
) {
|
|
459
|
-
subscriptionUpdateParamsBuilder.setReplaceSkusProrationMode(
|
|
460
|
-
BillingFlowParams.ProrationMode.IMMEDIATE_WITHOUT_PRORATION
|
|
461
|
-
)
|
|
462
|
-
} else if (prorationMode
|
|
463
|
-
== BillingFlowParams.ProrationMode.IMMEDIATE_AND_CHARGE_FULL_PRICE
|
|
464
|
-
) {
|
|
465
|
-
subscriptionUpdateParamsBuilder.setReplaceSkusProrationMode(
|
|
466
|
-
BillingFlowParams.ProrationMode.IMMEDIATE_AND_CHARGE_FULL_PRICE
|
|
467
|
-
)
|
|
468
|
-
} else {
|
|
469
|
-
subscriptionUpdateParamsBuilder.setReplaceSkusProrationMode(
|
|
470
|
-
BillingFlowParams.ProrationMode.UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY
|
|
471
|
-
)
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
if (purchaseToken != null) {
|
|
475
|
-
val subscriptionUpdateParams = subscriptionUpdateParamsBuilder.build()
|
|
476
|
-
builder.setSubscriptionUpdateParams(subscriptionUpdateParams)
|
|
423
|
+
return@ensureConnection
|
|
477
424
|
}
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
425
|
+
} else if (prorationMode
|
|
426
|
+
== BillingFlowParams.ProrationMode.IMMEDIATE_WITHOUT_PRORATION
|
|
427
|
+
) {
|
|
428
|
+
subscriptionUpdateParamsBuilder.setReplaceSkusProrationMode(
|
|
429
|
+
BillingFlowParams.ProrationMode.IMMEDIATE_WITHOUT_PRORATION
|
|
430
|
+
)
|
|
431
|
+
} else if (prorationMode == BillingFlowParams.ProrationMode.DEFERRED) {
|
|
432
|
+
subscriptionUpdateParamsBuilder.setReplaceSkusProrationMode(
|
|
433
|
+
BillingFlowParams.ProrationMode.DEFERRED
|
|
434
|
+
)
|
|
435
|
+
} else if (prorationMode
|
|
436
|
+
== BillingFlowParams.ProrationMode.IMMEDIATE_WITH_TIME_PRORATION
|
|
437
|
+
) {
|
|
438
|
+
subscriptionUpdateParamsBuilder.setReplaceSkusProrationMode(
|
|
439
|
+
BillingFlowParams.ProrationMode.IMMEDIATE_WITHOUT_PRORATION
|
|
440
|
+
)
|
|
441
|
+
} else if (prorationMode
|
|
442
|
+
== BillingFlowParams.ProrationMode.IMMEDIATE_AND_CHARGE_FULL_PRICE
|
|
443
|
+
) {
|
|
444
|
+
subscriptionUpdateParamsBuilder.setReplaceSkusProrationMode(
|
|
445
|
+
BillingFlowParams.ProrationMode.IMMEDIATE_AND_CHARGE_FULL_PRICE
|
|
446
|
+
)
|
|
447
|
+
} else {
|
|
448
|
+
subscriptionUpdateParamsBuilder.setReplaceSkusProrationMode(
|
|
449
|
+
BillingFlowParams.ProrationMode.UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY
|
|
450
|
+
)
|
|
482
451
|
}
|
|
483
452
|
}
|
|
484
|
-
|
|
453
|
+
if (purchaseToken != null) {
|
|
454
|
+
val subscriptionUpdateParams = subscriptionUpdateParamsBuilder.build()
|
|
455
|
+
builder.setSubscriptionUpdateParams(subscriptionUpdateParams)
|
|
456
|
+
}
|
|
457
|
+
val flowParams = builder.build()
|
|
458
|
+
val billingResult = billingClient.launchBillingFlow(activity, flowParams)
|
|
459
|
+
val errorData: Array<String?> =
|
|
460
|
+
PlayUtils.instance.getBillingResponseData(billingResult.responseCode)
|
|
461
|
+
}
|
|
485
462
|
}
|
|
486
463
|
|
|
487
464
|
@ReactMethod
|
|
@@ -491,36 +468,33 @@ class RNIapModule(reactContext: ReactApplicationContext) :
|
|
|
491
468
|
promise: Promise
|
|
492
469
|
) {
|
|
493
470
|
ensureConnection(
|
|
494
|
-
promise
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
Log.e(TAG, oce.message!!)
|
|
519
|
-
}
|
|
520
|
-
}
|
|
471
|
+
promise
|
|
472
|
+
) {
|
|
473
|
+
val acknowledgePurchaseParams =
|
|
474
|
+
AcknowledgePurchaseParams.newBuilder().setPurchaseToken(
|
|
475
|
+
token!!
|
|
476
|
+
).build()
|
|
477
|
+
billingClient.acknowledgePurchase(
|
|
478
|
+
acknowledgePurchaseParams
|
|
479
|
+
) { billingResult: BillingResult ->
|
|
480
|
+
if (billingResult.responseCode != BillingClient.BillingResponseCode.OK) {
|
|
481
|
+
PlayUtils.instance
|
|
482
|
+
.rejectPromiseWithBillingError(promise, billingResult.responseCode)
|
|
483
|
+
}
|
|
484
|
+
try {
|
|
485
|
+
val map = Arguments.createMap()
|
|
486
|
+
map.putInt("responseCode", billingResult.responseCode)
|
|
487
|
+
map.putString("debugMessage", billingResult.debugMessage)
|
|
488
|
+
val errorData: Array<String?> = PlayUtils.instance
|
|
489
|
+
.getBillingResponseData(billingResult.responseCode)
|
|
490
|
+
map.putString("code", errorData[0])
|
|
491
|
+
map.putString("message", errorData[1])
|
|
492
|
+
promise.resolve(map)
|
|
493
|
+
} catch (oce: ObjectAlreadyConsumedException) {
|
|
494
|
+
Log.e(TAG, oce.message!!)
|
|
521
495
|
}
|
|
522
496
|
}
|
|
523
|
-
|
|
497
|
+
}
|
|
524
498
|
}
|
|
525
499
|
|
|
526
500
|
@ReactMethod
|
|
@@ -531,32 +505,29 @@ class RNIapModule(reactContext: ReactApplicationContext) :
|
|
|
531
505
|
) {
|
|
532
506
|
val params = ConsumeParams.newBuilder().setPurchaseToken(token!!).build()
|
|
533
507
|
ensureConnection(
|
|
534
|
-
promise
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
promise.reject(oce.message)
|
|
555
|
-
}
|
|
556
|
-
}
|
|
508
|
+
promise
|
|
509
|
+
) {
|
|
510
|
+
billingClient.consumeAsync(
|
|
511
|
+
params
|
|
512
|
+
) { billingResult: BillingResult, purchaseToken: String? ->
|
|
513
|
+
if (billingResult.responseCode != BillingClient.BillingResponseCode.OK) {
|
|
514
|
+
PlayUtils.instance
|
|
515
|
+
.rejectPromiseWithBillingError(promise, billingResult.responseCode)
|
|
516
|
+
}
|
|
517
|
+
try {
|
|
518
|
+
val map = Arguments.createMap()
|
|
519
|
+
map.putInt("responseCode", billingResult.responseCode)
|
|
520
|
+
map.putString("debugMessage", billingResult.debugMessage)
|
|
521
|
+
val errorData: Array<String?> = PlayUtils.instance
|
|
522
|
+
.getBillingResponseData(billingResult.responseCode)
|
|
523
|
+
map.putString("code", errorData[0])
|
|
524
|
+
map.putString("message", errorData[1])
|
|
525
|
+
promise.resolve(map)
|
|
526
|
+
} catch (oce: ObjectAlreadyConsumedException) {
|
|
527
|
+
promise.reject(oce.message)
|
|
557
528
|
}
|
|
558
529
|
}
|
|
559
|
-
|
|
530
|
+
}
|
|
560
531
|
}
|
|
561
532
|
|
|
562
533
|
override fun onPurchasesUpdated(billingResult: BillingResult, purchases: List<Purchase>?) {
|
|
@@ -624,30 +595,25 @@ class RNIapModule(reactContext: ReactApplicationContext) :
|
|
|
624
595
|
|
|
625
596
|
private fun sendUnconsumedPurchases(promise: Promise) {
|
|
626
597
|
ensureConnection(
|
|
627
|
-
promise
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
for (purchase in list!!) {
|
|
640
|
-
if (!purchase.isAcknowledged) {
|
|
641
|
-
unacknowledgedPurchases.add(purchase)
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
onPurchasesUpdated(billingResult, unacknowledgedPurchases)
|
|
598
|
+
promise
|
|
599
|
+
) {
|
|
600
|
+
val types = arrayOf(BillingClient.SkuType.INAPP, BillingClient.SkuType.SUBS)
|
|
601
|
+
for (type in types) {
|
|
602
|
+
billingClient.queryPurchasesAsync(
|
|
603
|
+
type
|
|
604
|
+
) { billingResult: BillingResult, list: List<Purchase>? ->
|
|
605
|
+
val unacknowledgedPurchases = ArrayList<Purchase>()
|
|
606
|
+
|
|
607
|
+
for (purchase in list!!) {
|
|
608
|
+
if (!purchase.isAcknowledged) {
|
|
609
|
+
unacknowledgedPurchases.add(purchase)
|
|
645
610
|
}
|
|
646
611
|
}
|
|
647
|
-
|
|
612
|
+
onPurchasesUpdated(billingResult, unacknowledgedPurchases)
|
|
648
613
|
}
|
|
649
614
|
}
|
|
650
|
-
|
|
615
|
+
promise.resolve(true)
|
|
616
|
+
}
|
|
651
617
|
}
|
|
652
618
|
|
|
653
619
|
@ReactMethod
|
|
@@ -681,19 +647,15 @@ class RNIapModule(reactContext: ReactApplicationContext) :
|
|
|
681
647
|
|
|
682
648
|
companion object {
|
|
683
649
|
private const val PROMISE_BUY_ITEM = "PROMISE_BUY_ITEM"
|
|
650
|
+
const val TAG = "RNIapModule"
|
|
684
651
|
}
|
|
685
652
|
|
|
686
653
|
init {
|
|
687
|
-
this.reactContext = reactContext
|
|
688
|
-
skus = mutableMapOf<String, SkuDetails>()
|
|
689
654
|
val lifecycleEventListener: LifecycleEventListener = object : LifecycleEventListener {
|
|
690
655
|
override fun onHostResume() {}
|
|
691
656
|
override fun onHostPause() {}
|
|
692
657
|
override fun onHostDestroy() {
|
|
693
|
-
|
|
694
|
-
billingClientCache!!.endConnection()
|
|
695
|
-
billingClientCache = null
|
|
696
|
-
}
|
|
658
|
+
billingClient.endConnection()
|
|
697
659
|
}
|
|
698
660
|
}
|
|
699
661
|
reactContext.addLifecycleEventListener(lifecycleEventListener)
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
package com.dooboolab.RNIap
|
|
2
|
+
|
|
3
|
+
import com.android.billingclient.api.BillingClient
|
|
4
|
+
import com.android.billingclient.api.BillingClientStateListener
|
|
5
|
+
import com.android.billingclient.api.BillingResult
|
|
6
|
+
import com.android.billingclient.api.PurchasesResponseListener
|
|
7
|
+
import com.facebook.react.bridge.Promise
|
|
8
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
9
|
+
import com.google.android.gms.common.ConnectionResult
|
|
10
|
+
import com.google.android.gms.common.GoogleApiAvailability
|
|
11
|
+
import io.mockk.MockKAnnotations
|
|
12
|
+
import io.mockk.every
|
|
13
|
+
import io.mockk.impl.annotations.MockK
|
|
14
|
+
import io.mockk.mockk
|
|
15
|
+
import io.mockk.slot
|
|
16
|
+
import io.mockk.verify
|
|
17
|
+
import org.junit.Before
|
|
18
|
+
import org.junit.Test
|
|
19
|
+
|
|
20
|
+
class RNIapModuleTest {
|
|
21
|
+
|
|
22
|
+
@MockK
|
|
23
|
+
lateinit var context: ReactApplicationContext
|
|
24
|
+
@MockK
|
|
25
|
+
lateinit var builder: BillingClient.Builder
|
|
26
|
+
@MockK
|
|
27
|
+
lateinit var billingClient: BillingClient
|
|
28
|
+
@MockK
|
|
29
|
+
lateinit var availability: GoogleApiAvailability
|
|
30
|
+
|
|
31
|
+
private lateinit var module: RNIapModule
|
|
32
|
+
|
|
33
|
+
@Before
|
|
34
|
+
fun setUp() {
|
|
35
|
+
MockKAnnotations.init(this, relaxUnitFun = true)
|
|
36
|
+
every { builder.setListener(any()) } returns builder
|
|
37
|
+
every { builder.build() } returns billingClient
|
|
38
|
+
module = RNIapModule(context, builder, availability)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@Test
|
|
42
|
+
fun `initConnection Already connected should resolve to true`() {
|
|
43
|
+
every { billingClient.isReady } returns true
|
|
44
|
+
val promise = mockk<Promise>(relaxed = true)
|
|
45
|
+
|
|
46
|
+
module.initConnection(promise)
|
|
47
|
+
verify(exactly = 0) { promise.reject(any(), any<String>()) }
|
|
48
|
+
verify { promise.resolve(true) }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@Test
|
|
52
|
+
fun `initConnection Play Services not available on device should reject`() {
|
|
53
|
+
every { billingClient.isReady } returns false
|
|
54
|
+
every { availability.isGooglePlayServicesAvailable(any()) } returns ConnectionResult.DEVELOPER_ERROR
|
|
55
|
+
val promise = mockk<Promise>(relaxed = true)
|
|
56
|
+
|
|
57
|
+
module.initConnection(promise)
|
|
58
|
+
verify { promise.reject(any(), any<String>()) }
|
|
59
|
+
verify(exactly = 0) { promise.resolve(any()) }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@Test
|
|
63
|
+
fun `initConnection start new connection succeeds`() {
|
|
64
|
+
every { billingClient.isReady } returns false
|
|
65
|
+
val listener = slot<BillingClientStateListener>()
|
|
66
|
+
every { billingClient.startConnection(capture(listener)) } answers {
|
|
67
|
+
listener.captured.onBillingSetupFinished(BillingResult.newBuilder().setResponseCode(BillingClient.BillingResponseCode.OK).build())
|
|
68
|
+
}
|
|
69
|
+
every { availability.isGooglePlayServicesAvailable(any()) } returns ConnectionResult.SUCCESS
|
|
70
|
+
val promise = mockk<Promise>(relaxed = true)
|
|
71
|
+
|
|
72
|
+
module.initConnection(promise)
|
|
73
|
+
verify(exactly = 0) { promise.reject(any(), any<String>()) }
|
|
74
|
+
verify { promise.resolve(any()) }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
@Test
|
|
78
|
+
fun `initConnection start new connection fails`() {
|
|
79
|
+
every { billingClient.isReady } returns false
|
|
80
|
+
val listener = slot<BillingClientStateListener>()
|
|
81
|
+
every { billingClient.startConnection(capture(listener)) } answers {
|
|
82
|
+
listener.captured.onBillingSetupFinished(BillingResult.newBuilder().setResponseCode(BillingClient.BillingResponseCode.ERROR).build())
|
|
83
|
+
}
|
|
84
|
+
every { availability.isGooglePlayServicesAvailable(any()) } returns ConnectionResult.SUCCESS
|
|
85
|
+
val promise = mockk<Promise>(relaxed = true)
|
|
86
|
+
|
|
87
|
+
module.initConnection(promise)
|
|
88
|
+
verify { promise.reject(any(), any<String>()) }
|
|
89
|
+
verify(exactly = 0) { promise.resolve(any()) }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
@Test
|
|
93
|
+
fun `endConnection resolves`() {
|
|
94
|
+
val promise = mockk<Promise>(relaxed = true)
|
|
95
|
+
|
|
96
|
+
module.endConnection(promise)
|
|
97
|
+
|
|
98
|
+
verify { billingClient.endConnection() }
|
|
99
|
+
verify(exactly = 0) { promise.reject(any(), any<String>()) }
|
|
100
|
+
verify { promise.resolve(true) }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@Test
|
|
104
|
+
fun `flushFailedPurchasesCachedAsPending resolves to false if no pending purchases`() {
|
|
105
|
+
every { billingClient.isReady } returns true
|
|
106
|
+
val promise = mockk<Promise>(relaxed = true)
|
|
107
|
+
val listener = slot<PurchasesResponseListener>()
|
|
108
|
+
every { billingClient.queryPurchasesAsync(any(), capture(listener)) } answers {
|
|
109
|
+
listener.captured.onQueryPurchasesResponse(BillingResult.newBuilder().build(), listOf())
|
|
110
|
+
}
|
|
111
|
+
module.flushFailedPurchasesCachedAsPending(promise)
|
|
112
|
+
|
|
113
|
+
verify(exactly = 0) { promise.reject(any(), any<String>()) }
|
|
114
|
+
verify { promise.resolve(false) } // empty list
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
@Test
|
|
118
|
+
fun getItemsByType() {
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
@Test
|
|
122
|
+
fun getAvailableItemsByType() {
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
@Test
|
|
126
|
+
fun getPurchaseHistoryByType() {
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
@Test
|
|
130
|
+
fun buyItemByType() {
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
@Test
|
|
134
|
+
fun acknowledgePurchase() {
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
@Test
|
|
138
|
+
fun consumeProduct() {
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
@Test
|
|
142
|
+
fun onPurchasesUpdated() {
|
|
143
|
+
}
|
|
144
|
+
}
|