react-native-iap 8.2.0 → 8.2.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/.yarn/install-state.gz +0 -0
- package/android/src/play/java/com/dooboolab/RNIap/PlayUtils.kt +1 -1
- package/android/src/play/java/com/dooboolab/RNIap/PromiseUtlis.kt +33 -0
- package/android/src/play/java/com/dooboolab/RNIap/RNIapModule.kt +39 -65
- package/android/src/testPlay/java/com/dooboolab/RNIap/RNIapModuleTest.kt +50 -2
- package/package.json +1 -1
package/.yarn/install-state.gz
CHANGED
|
Binary file
|
|
@@ -7,7 +7,7 @@ import com.facebook.react.bridge.Promise
|
|
|
7
7
|
class PlayUtils {
|
|
8
8
|
fun rejectPromiseWithBillingError(promise: Promise, responseCode: Int) {
|
|
9
9
|
val errorData = getBillingResponseData(responseCode)
|
|
10
|
-
promise.
|
|
10
|
+
promise.safeReject(errorData[0], errorData[1])
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
fun getBillingResponseData(responseCode: Int): Array<String?> {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
package com.dooboolab.RNIap
|
|
2
|
+
|
|
3
|
+
import android.util.Log
|
|
4
|
+
import com.facebook.react.bridge.ObjectAlreadyConsumedException
|
|
5
|
+
import com.facebook.react.bridge.Promise
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Extension functions used to simplify promise handling since we don't
|
|
9
|
+
* want to crash in the case of it being resolved/rejected more than once
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
fun Promise.safeResolve(value: Any) {
|
|
13
|
+
try {
|
|
14
|
+
this.resolve(value)
|
|
15
|
+
} catch (oce: ObjectAlreadyConsumedException) {
|
|
16
|
+
Log.d(RNIapModule.TAG, "Already consumed ${oce.message}")
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
fun Promise.safeReject(message: String) = this.safeReject(message, null, null)
|
|
21
|
+
|
|
22
|
+
fun Promise.safeReject(code: String?, message: String?) = this.safeReject(code, message, null)
|
|
23
|
+
|
|
24
|
+
fun Promise.safeReject(code: String?, throwable: Throwable?) =
|
|
25
|
+
this.safeReject(code, null, throwable)
|
|
26
|
+
|
|
27
|
+
fun Promise.safeReject(code: String?, message: String?, throwable: Throwable?) {
|
|
28
|
+
try {
|
|
29
|
+
this.reject(code, message, throwable)
|
|
30
|
+
} catch (oce: ObjectAlreadyConsumedException) {
|
|
31
|
+
Log.d(RNIapModule.TAG, "Already consumed ${oce.message}")
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -15,7 +15,6 @@ import com.android.billingclient.api.SkuDetails
|
|
|
15
15
|
import com.android.billingclient.api.SkuDetailsParams
|
|
16
16
|
import com.facebook.react.bridge.Arguments
|
|
17
17
|
import com.facebook.react.bridge.LifecycleEventListener
|
|
18
|
-
import com.facebook.react.bridge.ObjectAlreadyConsumedException
|
|
19
18
|
import com.facebook.react.bridge.Promise
|
|
20
19
|
import com.facebook.react.bridge.PromiseImpl
|
|
21
20
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
@@ -46,7 +45,7 @@ class RNIapModule(
|
|
|
46
45
|
return "RNIapModule"
|
|
47
46
|
}
|
|
48
47
|
|
|
49
|
-
|
|
48
|
+
internal fun ensureConnection(promise: Promise, callback: () -> Unit) {
|
|
50
49
|
if (billingClient.isReady) {
|
|
51
50
|
callback()
|
|
52
51
|
return
|
|
@@ -61,7 +60,7 @@ class RNIapModule(
|
|
|
61
60
|
},
|
|
62
61
|
{
|
|
63
62
|
if (it.size > 1 && it[0] is String && it[1] is String) {
|
|
64
|
-
promise.
|
|
63
|
+
promise.safeReject(
|
|
65
64
|
it[0] as String, it[1] as String
|
|
66
65
|
)
|
|
67
66
|
} else {
|
|
@@ -80,14 +79,14 @@ class RNIapModule(
|
|
|
80
79
|
TAG,
|
|
81
80
|
"Already initialized, you should only call initConnection() once when your app starts"
|
|
82
81
|
)
|
|
83
|
-
promise.
|
|
82
|
+
promise.safeResolve(true)
|
|
84
83
|
return
|
|
85
84
|
}
|
|
86
85
|
if (googleApiAvailability.isGooglePlayServicesAvailable(reactContext)
|
|
87
86
|
!= ConnectionResult.SUCCESS
|
|
88
87
|
) {
|
|
89
88
|
Log.i(TAG, "Google Play Services are not available on this device")
|
|
90
|
-
promise.
|
|
89
|
+
promise.safeReject(DoobooUtils.E_NOT_PREPARED, "Google Play Services are not available on this device")
|
|
91
90
|
return
|
|
92
91
|
}
|
|
93
92
|
|
|
@@ -97,15 +96,12 @@ class RNIapModule(
|
|
|
97
96
|
object : BillingClientStateListener {
|
|
98
97
|
override fun onBillingSetupFinished(billingResult: BillingResult) {
|
|
99
98
|
val responseCode = billingResult.responseCode
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
} catch (oce: ObjectAlreadyConsumedException) {
|
|
108
|
-
Log.e(TAG, oce.message!!)
|
|
99
|
+
|
|
100
|
+
if (responseCode == BillingClient.BillingResponseCode.OK) {
|
|
101
|
+
promise.safeResolve(true)
|
|
102
|
+
} else {
|
|
103
|
+
PlayUtils.instance
|
|
104
|
+
.rejectPromiseWithBillingError(promise, responseCode)
|
|
109
105
|
}
|
|
110
106
|
}
|
|
111
107
|
|
|
@@ -118,7 +114,7 @@ class RNIapModule(
|
|
|
118
114
|
@ReactMethod
|
|
119
115
|
fun endConnection(promise: Promise) {
|
|
120
116
|
billingClient.endConnection()
|
|
121
|
-
promise.
|
|
117
|
+
promise.safeResolve(true)
|
|
122
118
|
}
|
|
123
119
|
|
|
124
120
|
private fun consumeItems(
|
|
@@ -143,11 +139,8 @@ class RNIapModule(
|
|
|
143
139
|
)
|
|
144
140
|
return@ConsumeResponseListener
|
|
145
141
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
} catch (oce: ObjectAlreadyConsumedException) {
|
|
149
|
-
promise.reject(oce.message)
|
|
150
|
-
}
|
|
142
|
+
|
|
143
|
+
promise.safeResolve(true)
|
|
151
144
|
}
|
|
152
145
|
billingClient.consumeAsync(consumeParams, listener)
|
|
153
146
|
}
|
|
@@ -164,7 +157,7 @@ class RNIapModule(
|
|
|
164
157
|
) { _: BillingResult?, list: List<Purchase>? ->
|
|
165
158
|
if (list == null) {
|
|
166
159
|
// No purchases found
|
|
167
|
-
promise.
|
|
160
|
+
promise.safeResolve(false)
|
|
168
161
|
return@queryPurchasesAsync
|
|
169
162
|
}
|
|
170
163
|
// we only want to try to consume PENDING items, in order to force cache-refresh
|
|
@@ -172,7 +165,7 @@ class RNIapModule(
|
|
|
172
165
|
val pendingPurchases = list.filter { it.purchaseState == Purchase.PurchaseState.PENDING }
|
|
173
166
|
|
|
174
167
|
if (pendingPurchases.isEmpty()) {
|
|
175
|
-
promise.
|
|
168
|
+
promise.safeResolve(false)
|
|
176
169
|
return@queryPurchasesAsync
|
|
177
170
|
}
|
|
178
171
|
consumeItems(
|
|
@@ -262,11 +255,7 @@ class RNIapModule(
|
|
|
262
255
|
item.putString("originalPrice", originalPrice)
|
|
263
256
|
items.pushMap(item)
|
|
264
257
|
}
|
|
265
|
-
|
|
266
|
-
promise.resolve(items)
|
|
267
|
-
} catch (oce: ObjectAlreadyConsumedException) {
|
|
268
|
-
Log.e(TAG, oce.message!!)
|
|
269
|
-
}
|
|
258
|
+
promise.safeResolve(items)
|
|
270
259
|
}
|
|
271
260
|
}
|
|
272
261
|
}
|
|
@@ -309,11 +298,7 @@ class RNIapModule(
|
|
|
309
298
|
items.pushMap(item)
|
|
310
299
|
}
|
|
311
300
|
}
|
|
312
|
-
|
|
313
|
-
promise.resolve(items)
|
|
314
|
-
} catch (oce: ObjectAlreadyConsumedException) {
|
|
315
|
-
Log.e(TAG, oce.message!!)
|
|
316
|
-
}
|
|
301
|
+
promise.safeResolve(items)
|
|
317
302
|
}
|
|
318
303
|
}
|
|
319
304
|
}
|
|
@@ -345,11 +330,7 @@ class RNIapModule(
|
|
|
345
330
|
item.putString("developerPayload", purchase.developerPayload)
|
|
346
331
|
items.pushMap(item)
|
|
347
332
|
}
|
|
348
|
-
|
|
349
|
-
promise.resolve(items)
|
|
350
|
-
} catch (oce: ObjectAlreadyConsumedException) {
|
|
351
|
-
Log.e(TAG, oce.message!!)
|
|
352
|
-
}
|
|
333
|
+
promise.safeResolve(items)
|
|
353
334
|
}
|
|
354
335
|
}
|
|
355
336
|
}
|
|
@@ -366,7 +347,7 @@ class RNIapModule(
|
|
|
366
347
|
) {
|
|
367
348
|
val activity = currentActivity
|
|
368
349
|
if (activity == null) {
|
|
369
|
-
promise.
|
|
350
|
+
promise.safeReject(DoobooUtils.E_UNKNOWN, "getCurrentActivity returned null")
|
|
370
351
|
return
|
|
371
352
|
}
|
|
372
353
|
ensureConnection(
|
|
@@ -386,7 +367,7 @@ class RNIapModule(
|
|
|
386
367
|
error.putString("message", debugMessage)
|
|
387
368
|
error.putString("productId", sku)
|
|
388
369
|
sendEvent(reactContext, "purchase-error", error)
|
|
389
|
-
promise.
|
|
370
|
+
promise.safeReject(PROMISE_BUY_ITEM, debugMessage)
|
|
390
371
|
return@ensureConnection
|
|
391
372
|
}
|
|
392
373
|
builder.setSkuDetails(selectedSku)
|
|
@@ -419,7 +400,7 @@ class RNIapModule(
|
|
|
419
400
|
error.putString("message", debugMessage)
|
|
420
401
|
error.putString("productId", sku)
|
|
421
402
|
sendEvent(reactContext, "purchase-error", error)
|
|
422
|
-
promise.
|
|
403
|
+
promise.safeReject(PROMISE_BUY_ITEM, debugMessage)
|
|
423
404
|
return@ensureConnection
|
|
424
405
|
}
|
|
425
406
|
} else if (prorationMode
|
|
@@ -481,18 +462,14 @@ class RNIapModule(
|
|
|
481
462
|
PlayUtils.instance
|
|
482
463
|
.rejectPromiseWithBillingError(promise, billingResult.responseCode)
|
|
483
464
|
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
promise.resolve(map)
|
|
493
|
-
} catch (oce: ObjectAlreadyConsumedException) {
|
|
494
|
-
Log.e(TAG, oce.message!!)
|
|
495
|
-
}
|
|
465
|
+
val map = Arguments.createMap()
|
|
466
|
+
map.putInt("responseCode", billingResult.responseCode)
|
|
467
|
+
map.putString("debugMessage", billingResult.debugMessage)
|
|
468
|
+
val errorData: Array<String?> = PlayUtils.instance
|
|
469
|
+
.getBillingResponseData(billingResult.responseCode)
|
|
470
|
+
map.putString("code", errorData[0])
|
|
471
|
+
map.putString("message", errorData[1])
|
|
472
|
+
promise.safeResolve(map)
|
|
496
473
|
}
|
|
497
474
|
}
|
|
498
475
|
}
|
|
@@ -514,18 +491,15 @@ class RNIapModule(
|
|
|
514
491
|
PlayUtils.instance
|
|
515
492
|
.rejectPromiseWithBillingError(promise, billingResult.responseCode)
|
|
516
493
|
}
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
} catch (oce: ObjectAlreadyConsumedException) {
|
|
527
|
-
promise.reject(oce.message)
|
|
528
|
-
}
|
|
494
|
+
|
|
495
|
+
val map = Arguments.createMap()
|
|
496
|
+
map.putInt("responseCode", billingResult.responseCode)
|
|
497
|
+
map.putString("debugMessage", billingResult.debugMessage)
|
|
498
|
+
val errorData: Array<String?> = PlayUtils.instance
|
|
499
|
+
.getBillingResponseData(billingResult.responseCode)
|
|
500
|
+
map.putString("code", errorData[0])
|
|
501
|
+
map.putString("message", errorData[1])
|
|
502
|
+
promise.safeResolve(map)
|
|
529
503
|
}
|
|
530
504
|
}
|
|
531
505
|
}
|
|
@@ -612,7 +586,7 @@ class RNIapModule(
|
|
|
612
586
|
onPurchasesUpdated(billingResult, unacknowledgedPurchases)
|
|
613
587
|
}
|
|
614
588
|
}
|
|
615
|
-
promise.
|
|
589
|
+
promise.safeResolve(true)
|
|
616
590
|
}
|
|
617
591
|
}
|
|
618
592
|
|
|
@@ -3,6 +3,8 @@ package com.dooboolab.RNIap
|
|
|
3
3
|
import com.android.billingclient.api.BillingClient
|
|
4
4
|
import com.android.billingclient.api.BillingClientStateListener
|
|
5
5
|
import com.android.billingclient.api.BillingResult
|
|
6
|
+
import com.android.billingclient.api.ConsumeResponseListener
|
|
7
|
+
import com.android.billingclient.api.Purchase
|
|
6
8
|
import com.android.billingclient.api.PurchasesResponseListener
|
|
7
9
|
import com.facebook.react.bridge.Promise
|
|
8
10
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
@@ -14,6 +16,7 @@ import io.mockk.impl.annotations.MockK
|
|
|
14
16
|
import io.mockk.mockk
|
|
15
17
|
import io.mockk.slot
|
|
16
18
|
import io.mockk.verify
|
|
19
|
+
import org.junit.Assert.assertTrue
|
|
17
20
|
import org.junit.Before
|
|
18
21
|
import org.junit.Test
|
|
19
22
|
|
|
@@ -55,7 +58,7 @@ class RNIapModuleTest {
|
|
|
55
58
|
val promise = mockk<Promise>(relaxed = true)
|
|
56
59
|
|
|
57
60
|
module.initConnection(promise)
|
|
58
|
-
verify { promise.
|
|
61
|
+
verify { promise.safeReject(any(), any<String>()) }
|
|
59
62
|
verify(exactly = 0) { promise.resolve(any()) }
|
|
60
63
|
}
|
|
61
64
|
|
|
@@ -85,7 +88,7 @@ class RNIapModuleTest {
|
|
|
85
88
|
val promise = mockk<Promise>(relaxed = true)
|
|
86
89
|
|
|
87
90
|
module.initConnection(promise)
|
|
88
|
-
verify { promise.
|
|
91
|
+
verify { promise.safeReject(any(), any<String>()) }
|
|
89
92
|
verify(exactly = 0) { promise.resolve(any()) }
|
|
90
93
|
}
|
|
91
94
|
|
|
@@ -114,6 +117,51 @@ class RNIapModuleTest {
|
|
|
114
117
|
verify { promise.resolve(false) } // empty list
|
|
115
118
|
}
|
|
116
119
|
|
|
120
|
+
@Test
|
|
121
|
+
fun `flushFailedPurchasesCachedAsPending resolves to true if pending purchases`() {
|
|
122
|
+
every { billingClient.isReady } returns true
|
|
123
|
+
val promise = mockk<Promise>(relaxed = true)
|
|
124
|
+
val listener = slot<PurchasesResponseListener>()
|
|
125
|
+
every { billingClient.queryPurchasesAsync(any(), capture(listener)) } answers {
|
|
126
|
+
listener.captured.onQueryPurchasesResponse(
|
|
127
|
+
BillingResult.newBuilder().build(),
|
|
128
|
+
listOf(
|
|
129
|
+
// 4 = Pending
|
|
130
|
+
mockk<Purchase> {
|
|
131
|
+
every { purchaseState } returns 2
|
|
132
|
+
every { purchaseToken } returns "token"
|
|
133
|
+
},
|
|
134
|
+
Purchase("", "1")
|
|
135
|
+
)
|
|
136
|
+
)
|
|
137
|
+
}
|
|
138
|
+
val consumeListener = slot<ConsumeResponseListener>()
|
|
139
|
+
every { billingClient.consumeAsync(any(), capture(consumeListener)) } answers {
|
|
140
|
+
consumeListener.captured.onConsumeResponse(BillingResult.newBuilder().setResponseCode(BillingClient.BillingResponseCode.ITEM_NOT_OWNED).build(), "")
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
module.flushFailedPurchasesCachedAsPending(promise)
|
|
144
|
+
|
|
145
|
+
verify(exactly = 0) { promise.reject(any(), any<String>()) }
|
|
146
|
+
verify { promise.resolve(true) } // at least one pending transactions
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
@Test
|
|
150
|
+
fun `ensureConnection should attempt to reconnect, if not in ready state`() {
|
|
151
|
+
every { availability.isGooglePlayServicesAvailable(any()) } returns ConnectionResult.SUCCESS
|
|
152
|
+
val promise = mockk<Promise>(relaxed = true)
|
|
153
|
+
var isCallbackCalled = false
|
|
154
|
+
val callback = {
|
|
155
|
+
isCallbackCalled = true
|
|
156
|
+
promise.resolve(true)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
every { billingClient.isReady } returns false andThen true
|
|
160
|
+
module.ensureConnection(promise, callback)
|
|
161
|
+
verify { promise.resolve(true) } // at least one pending transactions
|
|
162
|
+
assertTrue("Should call callback", isCallbackCalled)
|
|
163
|
+
}
|
|
164
|
+
|
|
117
165
|
@Test
|
|
118
166
|
fun getItemsByType() {
|
|
119
167
|
}
|