react-native-iap 8.1.3 → 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.
Binary file
@@ -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 occured. Please try again later."
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
@@ -32,12 +32,15 @@ import com.google.android.gms.common.GoogleApiAvailability
32
32
  import java.math.BigDecimal
33
33
  import java.util.ArrayList
34
34
 
35
- class RNIapModule(private val reactContext: ReactApplicationContext) :
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
42
 
39
- private val billingClient: BillingClient = BillingClient.newBuilder(reactContext).enablePendingPurchases().setListener(this)
40
- .build()
43
+ private var billingClient: BillingClient = builder.setListener(this).build()
41
44
  private val skus: MutableMap<String, SkuDetails> = mutableMapOf()
42
45
  override fun getName(): String {
43
46
  return "RNIapModule"
@@ -80,7 +83,7 @@ class RNIapModule(private val reactContext: ReactApplicationContext) :
80
83
  promise.resolve(true)
81
84
  return
82
85
  }
83
- if (GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(reactContext)
86
+ if (googleApiAvailability.isGooglePlayServicesAvailable(reactContext)
84
87
  != ConnectionResult.SUCCESS
85
88
  ) {
86
89
  Log.i(TAG, "Google Play Services are not available on this device")
@@ -88,6 +91,8 @@ class RNIapModule(private val reactContext: ReactApplicationContext) :
88
91
  return
89
92
  }
90
93
 
94
+ billingClient = builder.setListener(this).build()
95
+
91
96
  billingClient.startConnection(
92
97
  object : BillingClientStateListener {
93
98
  override fun onBillingSetupFinished(billingResult: BillingResult) {
@@ -154,7 +159,6 @@ class RNIapModule(private val reactContext: ReactApplicationContext) :
154
159
  ensureConnection(
155
160
  promise
156
161
  ) {
157
- val array = WritableNativeArray()
158
162
  billingClient.queryPurchasesAsync(
159
163
  BillingClient.SkuType.INAPP
160
164
  ) { _: BillingResult?, list: List<Purchase>? ->
@@ -163,16 +167,11 @@ class RNIapModule(private val reactContext: ReactApplicationContext) :
163
167
  promise.resolve(false)
164
168
  return@queryPurchasesAsync
165
169
  }
166
- val pendingPurchases: MutableList<Purchase> = ArrayList()
167
- for (purchase in list) {
168
- // we only want to try to consume PENDING items, in order to force cache-refresh
169
- // for
170
- // them
171
- if (purchase.purchaseState == Purchase.PurchaseState.PENDING) {
172
- pendingPurchases.add(purchase)
173
- }
174
- }
175
- if (pendingPurchases.size == 0) {
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()) {
176
175
  promise.resolve(false)
177
176
  return@queryPurchasesAsync
178
177
  }
@@ -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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-iap",
3
- "version": "8.1.3",
3
+ "version": "8.2.0",
4
4
  "packageManager": "yarn@3.2.0",
5
5
  "description": "React Native In App Purchase Module.",
6
6
  "main": "index.js",