react-native-iap 8.2.2 → 8.5.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/README.md CHANGED
@@ -1,7 +1,8 @@
1
1
  # ![image](https://user-images.githubusercontent.com/27461460/75094417-20321b00-55ce-11ea-8de7-a1df42a4b7df.png)
2
2
 
3
3
  [![Version](http://img.shields.io/npm/v/react-native-iap.svg?style=flat-square)](https://npmjs.org/package/react-native-iap)
4
- <!-- [![Next](https://img.shields.io/npm/v/react-native-iap/next.svg?style=flat-square)](https://npmjs.org/package/react-native-iap) -->
4
+ [![Next Version](https://img.shields.io/npm/v/react-native-iap/next)](https://npmjs.org/package/react-native-iap)
5
+
5
6
  [![Download](http://img.shields.io/npm/dm/react-native-iap.svg?style=flat-square)](https://npmjs.org/package/react-native-iap)
6
7
  [![CI](https://github.com/dooboolab/react-native-iap/actions/workflows/ci.yml/badge.svg)](https://github.com/dooboolab/react-native-iap/actions/workflows/ci.yml)
7
8
  [![document](https://github.com/dooboolab/react-native-iap/actions/workflows/deploy-document.yml/badge.svg)](https://github.com/dooboolab/react-native-iap/actions/workflows/deploy-document.yml)
@@ -20,7 +21,11 @@ Published in [website](https://react-native-iap.dooboolab.com).
20
21
 
21
22
  ## Announcement
22
23
 
23
- - Current module is ported to [expo-iap](https://www.npmjs.com/package/expo-iap) which share the same codebase but different environment. This is currently experimental but you can use it in expo managed workflow. The source code is maintained in branch [expo](https://github.com/dooboolab/react-native-iap/tree/expo).
24
+ - Version `9.0.0` is currently in release candidate. The module migrates android sdk to [play billing library v5](https://qonversion.io/blog/google-play-billing-library-5-0) and iOS sdk to [storekit2](https://developer.apple.com/videos/play/wwdc2021/10114). Our core maintainer [andresesfm](https://github.com/andresesfm) is working hard on this. Please [fund the project](https://opencollective.com/react-native-iap) if you wish to support his effort. The fund goes to maintainers. To try the earlier version please use the `next` package.
25
+
26
+ ```
27
+ yarn add react-native-iap@next
28
+ ```
24
29
 
25
30
  - Version `8.0.0` has finally landed in Jan 28th. Since this is early release, please use it with caution 🚧. We recommend user to use `>=8.0.0` with react-native `>=0.65.1`. The `next` package is no longer updated until we organize the roadmap for `9.0.0`.
26
31
 
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
 
File without changes
@@ -0,0 +1,2 @@
1
+ #Wed Jul 27 12:39:50 KST 2022
2
+ gradle.version=6.8.3
File without changes
@@ -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 billingClient: BillingClient = builder.setListener(this).build()
43
+ private var billingClientCache: BillingClient? = null
43
44
  private val skus: MutableMap<String, SkuDetails> = mutableMapOf()
44
45
  override fun getName(): String {
45
46
  return TAG
46
47
  }
47
48
 
48
- internal fun ensureConnection(promise: Promise, callback: () -> Unit) {
49
- if (billingClient.isReady) {
50
- callback()
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
- callback()
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,25 +92,35 @@ class RNIapModule(
90
92
  return
91
93
  }
92
94
 
93
- billingClient = builder.setListener(this).build()
94
-
95
- billingClient.startConnection(
96
- object : BillingClientStateListener {
97
- override fun onBillingSetupFinished(billingResult: BillingResult) {
98
- if (!isValidResult(billingResult, promise)) return
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
- promise.safeResolve(true)
101
- }
110
+ promise.safeResolve(true)
111
+ }
102
112
 
103
- override fun onBillingServiceDisconnected() {
104
- Log.i(TAG, "Billing service disconnected")
105
- }
106
- })
113
+ override fun onBillingServiceDisconnected() {
114
+ Log.i(TAG, "Billing service disconnected")
115
+ }
116
+ })
117
+ }
107
118
  }
108
119
 
109
120
  @ReactMethod
110
121
  fun endConnection(promise: Promise) {
111
- billingClient.endConnection()
122
+ billingClientCache?.endConnection()
123
+ billingClientCache = null
112
124
  promise.safeResolve(true)
113
125
  }
114
126
 
@@ -120,7 +132,7 @@ class RNIapModule(
120
132
  for (purchase in purchases) {
121
133
  ensureConnection(
122
134
  promise
123
- ) {
135
+ ) { billingClient ->
124
136
  val consumeParams =
125
137
  ConsumeParams.newBuilder().setPurchaseToken(purchase.purchaseToken)
126
138
  .build()
@@ -146,7 +158,7 @@ class RNIapModule(
146
158
  fun flushFailedPurchasesCachedAsPending(promise: Promise) {
147
159
  ensureConnection(
148
160
  promise
149
- ) {
161
+ ) { billingClient ->
150
162
  billingClient.queryPurchasesAsync(
151
163
  BillingClient.SkuType.INAPP
152
164
  ) { billingResult: BillingResult, list: List<Purchase>? ->
@@ -177,11 +189,11 @@ class RNIapModule(
177
189
  fun getItemsByType(type: String, skuArr: ReadableArray, promise: Promise) {
178
190
  ensureConnection(
179
191
  promise
180
- ) {
192
+ ) { billingClient ->
181
193
  val skuList = ArrayList<String>()
182
194
  for (i in 0 until skuArr.size()) {
183
- val sku = skuArr.getString(i)
184
- if (sku is String) {
195
+ if (skuArr.getType(i) == ReadableType.String) {
196
+ val sku = skuArr.getString(i)
185
197
  skuList.add(sku)
186
198
  }
187
199
  }
@@ -192,61 +204,60 @@ class RNIapModule(
192
204
  ) { billingResult: BillingResult, skuDetailsList: List<SkuDetails>? ->
193
205
  if (!isValidResult(billingResult, promise)) return@querySkuDetailsAsync
194
206
 
207
+ val items = Arguments.createArray()
195
208
  if (skuDetailsList != null) {
196
- for (sku in skuDetailsList) {
197
- skus[sku.sku] = sku
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)
198
259
  }
199
260
  }
200
- val items = Arguments.createArray()
201
- for (skuDetails in skuDetailsList!!) {
202
- val item = Arguments.createMap()
203
- item.putString("productId", skuDetails.sku)
204
- val introductoryPriceMicros = skuDetails.introductoryPriceAmountMicros
205
- val priceAmountMicros = skuDetails.priceAmountMicros
206
- // Use valueOf instead of constructors.
207
- // See:
208
- // https://www.javaworld.com/article/2073176/caution--double-to-bigdecimal-in-java.html
209
- val priceAmount = BigDecimal.valueOf(priceAmountMicros)
210
- val introductoryPriceAmount =
211
- BigDecimal.valueOf(introductoryPriceMicros)
212
- val microUnitsDivisor = BigDecimal.valueOf(1000000)
213
- val price = priceAmount.divide(microUnitsDivisor).toString()
214
- val introductoryPriceAsAmountAndroid =
215
- introductoryPriceAmount.divide(microUnitsDivisor).toString()
216
- item.putString("price", price)
217
- item.putString("currency", skuDetails.priceCurrencyCode)
218
- item.putString("type", skuDetails.type)
219
- item.putString("localizedPrice", skuDetails.price)
220
- item.putString("title", skuDetails.title)
221
- item.putString("description", skuDetails.description)
222
- item.putString("introductoryPrice", skuDetails.introductoryPrice)
223
- item.putString("typeAndroid", skuDetails.type)
224
- item.putString("packageNameAndroid", skuDetails.zzc())
225
- item.putString("originalPriceAndroid", skuDetails.originalPrice)
226
- item.putString(
227
- "subscriptionPeriodAndroid",
228
- skuDetails.subscriptionPeriod
229
- )
230
- item.putString("freeTrialPeriodAndroid", skuDetails.freeTrialPeriod)
231
- item.putString(
232
- "introductoryPriceCyclesAndroid",
233
- skuDetails.introductoryPriceCycles.toString()
234
- )
235
- item.putString(
236
- "introductoryPricePeriodAndroid", skuDetails.introductoryPricePeriod
237
- )
238
- item.putString(
239
- "introductoryPriceAsAmountAndroid", introductoryPriceAsAmountAndroid
240
- )
241
- item.putString("iconUrl", skuDetails.iconUrl)
242
- item.putString("originalJson", skuDetails.originalJson)
243
- val originalPriceAmountMicros =
244
- BigDecimal.valueOf(skuDetails.originalPriceAmountMicros)
245
- val originalPrice =
246
- originalPriceAmountMicros.divide(microUnitsDivisor).toString()
247
- item.putString("originalPrice", originalPrice)
248
- items.pushMap(item)
249
- }
250
261
  promise.safeResolve(items)
251
262
  }
252
263
  }
@@ -272,7 +283,7 @@ class RNIapModule(
272
283
  fun getAvailableItemsByType(type: String, promise: Promise) {
273
284
  ensureConnection(
274
285
  promise
275
- ) {
286
+ ) { billingClient ->
276
287
  val items = WritableNativeArray()
277
288
  billingClient.queryPurchasesAsync(
278
289
  if (type == "subs") BillingClient.SkuType.SUBS else BillingClient.SkuType.INAPP
@@ -295,11 +306,11 @@ class RNIapModule(
295
306
  item.putString("packageNameAndroid", purchase.packageName)
296
307
  item.putString(
297
308
  "obfuscatedAccountIdAndroid",
298
- purchase.accountIdentifiers!!.obfuscatedAccountId
309
+ purchase.accountIdentifiers?.obfuscatedAccountId
299
310
  )
300
311
  item.putString(
301
312
  "obfuscatedProfileIdAndroid",
302
- purchase.accountIdentifiers!!.obfuscatedProfileId
313
+ purchase.accountIdentifiers?.obfuscatedProfileId
303
314
  )
304
315
  if (type == BillingClient.SkuType.SUBS) {
305
316
  item.putBoolean("autoRenewingAndroid", purchase.isAutoRenewing)
@@ -316,7 +327,7 @@ class RNIapModule(
316
327
  fun getPurchaseHistoryByType(type: String, promise: Promise) {
317
328
  ensureConnection(
318
329
  promise
319
- ) {
330
+ ) { billingClient ->
320
331
  billingClient.queryPurchaseHistoryAsync(
321
332
  if (type == "subs") BillingClient.SkuType.SUBS else BillingClient.SkuType.INAPP
322
333
  ) { billingResult, purchaseHistoryRecordList ->
@@ -357,7 +368,7 @@ class RNIapModule(
357
368
  }
358
369
  ensureConnection(
359
370
  promise
360
- ) {
371
+ ) { billingClient ->
361
372
  DoobooUtils.instance.addPromiseForKey(
362
373
  PROMISE_BUY_ITEM, promise
363
374
  )
@@ -441,13 +452,13 @@ class RNIapModule(
441
452
  builder.setSubscriptionUpdateParams(subscriptionUpdateParams)
442
453
  }
443
454
  val flowParams = builder.build()
444
- val billingResult = billingClient.launchBillingFlow(activity, flowParams)
445
- if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
455
+ val billingResultCode = billingClient.launchBillingFlow(activity, flowParams)?.responseCode ?: BillingClient.BillingResponseCode.ERROR
456
+ if (billingResultCode == BillingClient.BillingResponseCode.OK) {
446
457
  promise.safeResolve(true)
447
458
  return@ensureConnection
448
459
  } else {
449
460
  val errorData: Array<String?> =
450
- PlayUtils.instance.getBillingResponseData(billingResult.responseCode)
461
+ PlayUtils.instance.getBillingResponseData(billingResultCode)
451
462
  promise.safeReject(errorData[0], errorData[1])
452
463
  }
453
464
  }
@@ -455,16 +466,16 @@ class RNIapModule(
455
466
 
456
467
  @ReactMethod
457
468
  fun acknowledgePurchase(
458
- token: String?,
469
+ token: String,
459
470
  developerPayLoad: String?,
460
471
  promise: Promise
461
472
  ) {
462
473
  ensureConnection(
463
474
  promise
464
- ) {
475
+ ) { billingClient ->
465
476
  val acknowledgePurchaseParams =
466
477
  AcknowledgePurchaseParams.newBuilder().setPurchaseToken(
467
- token!!
478
+ token
468
479
  ).build()
469
480
  billingClient.acknowledgePurchase(
470
481
  acknowledgePurchaseParams
@@ -485,14 +496,14 @@ class RNIapModule(
485
496
 
486
497
  @ReactMethod
487
498
  fun consumeProduct(
488
- token: String?,
499
+ token: String,
489
500
  developerPayLoad: String?,
490
501
  promise: Promise
491
502
  ) {
492
- val params = ConsumeParams.newBuilder().setPurchaseToken(token!!).build()
503
+ val params = ConsumeParams.newBuilder().setPurchaseToken(token).build()
493
504
  ensureConnection(
494
505
  promise
495
- ) {
506
+ ) { billingClient ->
496
507
  billingClient.consumeAsync(
497
508
  params
498
509
  ) { billingResult: BillingResult, purchaseToken: String? ->
@@ -576,7 +587,7 @@ class RNIapModule(
576
587
  private fun sendUnconsumedPurchases(promise: Promise) {
577
588
  ensureConnection(
578
589
  promise
579
- ) {
590
+ ) { billingClient ->
580
591
  val types = arrayOf(BillingClient.SkuType.INAPP, BillingClient.SkuType.SUBS)
581
592
  for (type in types) {
582
593
  billingClient.queryPurchasesAsync(
@@ -607,9 +618,8 @@ class RNIapModule(
607
618
  // Keep: Required for RN built-in Event Emitter Calls.
608
619
  }
609
620
 
610
- @get:ReactMethod
611
- val packageName: String
612
- get() = reactApplicationContext.packageName
621
+ @ReactMethod
622
+ fun getPackageName(promise: Promise) = promise.resolve(reactApplicationContext.packageName)
613
623
 
614
624
  private fun sendEvent(
615
625
  reactContext: ReactContext,
@@ -631,7 +641,7 @@ class RNIapModule(
631
641
  override fun onHostResume() {}
632
642
  override fun onHostPause() {}
633
643
  override fun onHostDestroy() {
634
- billingClient.endConnection()
644
+ billingClientCache?.endConnection()
635
645
  }
636
646
  }
637
647
  reactContext.addLifecycleEventListener(lifecycleEventListener)
@@ -11,6 +11,7 @@ import com.facebook.react.bridge.Arguments
11
11
  import com.facebook.react.bridge.Promise
12
12
  import com.facebook.react.bridge.ReactApplicationContext
13
13
  import com.facebook.react.bridge.ReadableArray
14
+ import com.facebook.react.bridge.ReadableType
14
15
  import com.facebook.react.bridge.WritableArray
15
16
  import com.facebook.react.bridge.WritableMap
16
17
  import com.google.android.gms.common.ConnectionResult
@@ -55,9 +56,13 @@ class RNIapModuleTest {
55
56
 
56
57
  @Test
57
58
  fun `initConnection Already connected should resolve to true`() {
59
+ every { availability.isGooglePlayServicesAvailable(any()) } returns ConnectionResult.SUCCESS
60
+ module.initConnection(mockk())
61
+
58
62
  every { billingClient.isReady } returns true
59
- val promise = mockk<Promise>(relaxed = true)
60
63
 
64
+ val promise = mockk<Promise>(relaxed = true)
65
+ // Already connected
61
66
  module.initConnection(promise)
62
67
  verify(exactly = 0) { promise.reject(any(), any<String>()) }
63
68
  verify { promise.resolve(true) }
@@ -112,8 +117,10 @@ class RNIapModuleTest {
112
117
 
113
118
  @Test
114
119
  fun `endConnection resolves`() {
120
+ every { availability.isGooglePlayServicesAvailable(any()) } returns ConnectionResult.SUCCESS
115
121
  val promise = mockk<Promise>(relaxed = true)
116
122
 
123
+ module.initConnection(mockk())
117
124
  module.endConnection(promise)
118
125
 
119
126
  verify { billingClient.endConnection() }
@@ -123,12 +130,14 @@ class RNIapModuleTest {
123
130
 
124
131
  @Test
125
132
  fun `flushFailedPurchasesCachedAsPending resolves to false if no pending purchases`() {
133
+ every { availability.isGooglePlayServicesAvailable(any()) } returns ConnectionResult.SUCCESS
126
134
  every { billingClient.isReady } returns true
127
135
  val promise = mockk<Promise>(relaxed = true)
128
136
  val listener = slot<PurchasesResponseListener>()
129
137
  every { billingClient.queryPurchasesAsync(any(), capture(listener)) } answers {
130
138
  listener.captured.onQueryPurchasesResponse(BillingResult.newBuilder().build(), listOf())
131
139
  }
140
+ module.initConnection(mockk())
132
141
  module.flushFailedPurchasesCachedAsPending(promise)
133
142
 
134
143
  verify(exactly = 0) { promise.reject(any(), any<String>()) }
@@ -137,6 +146,7 @@ class RNIapModuleTest {
137
146
 
138
147
  @Test
139
148
  fun `flushFailedPurchasesCachedAsPending resolves to true if pending purchases`() {
149
+ every { availability.isGooglePlayServicesAvailable(any()) } returns ConnectionResult.SUCCESS
140
150
  every { billingClient.isReady } returns true
141
151
  val promise = mockk<Promise>(relaxed = true)
142
152
  val listener = slot<PurchasesResponseListener>()
@@ -161,7 +171,7 @@ class RNIapModuleTest {
161
171
  ""
162
172
  )
163
173
  }
164
-
174
+ module.initConnection(mockk())
165
175
  module.flushFailedPurchasesCachedAsPending(promise)
166
176
 
167
177
  verify(exactly = 0) { promise.reject(any(), any<String>()) }
@@ -173,12 +183,20 @@ class RNIapModuleTest {
173
183
  every { availability.isGooglePlayServicesAvailable(any()) } returns ConnectionResult.SUCCESS
174
184
  val promise = mockk<Promise>(relaxed = true)
175
185
  var isCallbackCalled = false
176
- val callback = {
186
+ val callback = { _: BillingClient ->
177
187
  isCallbackCalled = true
178
188
  promise.resolve(true)
179
189
  }
180
190
 
181
- every { billingClient.isReady } returns false andThen true
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
+
182
200
  module.ensureConnection(promise, callback)
183
201
  verify { promise.resolve(true) } // at least one pending transactions
184
202
  assertTrue("Should call callback", isCallbackCalled)
@@ -186,6 +204,7 @@ class RNIapModuleTest {
186
204
 
187
205
  @Test
188
206
  fun getItemsByType() {
207
+ every { availability.isGooglePlayServicesAvailable(any()) } returns ConnectionResult.SUCCESS
189
208
  every { billingClient.isReady } returns true
190
209
  val promise = mockk<Promise>(relaxed = true)
191
210
  val listener = slot<SkuDetailsResponseListener>()
@@ -219,6 +238,7 @@ class RNIapModuleTest {
219
238
  val skus = mockk<ReadableArray>() {
220
239
  every { size() } returns 1
221
240
  every { getString(0) } returns "sku0"
241
+ every { getType(0) } returns ReadableType.String
222
242
  }
223
243
  mockkStatic(Arguments::class)
224
244
 
@@ -231,7 +251,7 @@ class RNIapModuleTest {
231
251
  every { itemsArr.pushMap(any()) } answers {
232
252
  itemsSize++
233
253
  }
234
-
254
+ module.initConnection(mockk())
235
255
  module.getItemsByType("subs", skus, promise)
236
256
  verify { promise.resolve(any()) }
237
257
  assertEquals(itemsSize, 1)
package/ios/RNIapIos.m CHANGED
@@ -18,7 +18,9 @@ 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
23
+ applicationUsername:(NSString)applicationUsername
22
24
  resolve:(RCTPromiseResolveBlock)resolve
23
25
  reject:(RCTPromiseRejectBlock)reject)
24
26
  RCT_EXTERN_METHOD(buyProductWithOffer:
@@ -47,6 +47,7 @@ class RNIapIos: RCTEventEmitter, SKRequestDelegate, SKPaymentTransactionObserver
47
47
  myQueue = DispatchQueue(label: "reject")
48
48
  validProducts = [SKProduct]()
49
49
  super.init()
50
+ SKPaymentQueue.default().add(self)
50
51
  }
51
52
 
52
53
  deinit {
@@ -132,12 +133,6 @@ class RNIapIos: RCTEventEmitter, SKRequestDelegate, SKPaymentTransactionObserver
132
133
  _ resolve: @escaping RCTPromiseResolveBlock = { _ in },
133
134
  reject: @escaping RCTPromiseRejectBlock = { _, _, _ in }
134
135
  ) {
135
- SKPaymentQueue.default().remove(RNIapQueue.shared)
136
- if let queue = RNIapQueue.shared.queue, let payment = RNIapQueue.shared.payment, let product = RNIapQueue.shared.product {
137
- let val = paymentQueue(queue, shouldAddStorePayment: payment,for: product)
138
- print("Promoted product response \(val)")
139
- }
140
- SKPaymentQueue.default().add(self)
141
136
  let canMakePayments = SKPaymentQueue.canMakePayments()
142
137
  resolve(NSNumber(value: canMakePayments))
143
138
  }
@@ -179,6 +174,7 @@ class RNIapIos: RCTEventEmitter, SKRequestDelegate, SKPaymentTransactionObserver
179
174
  @objc public func buyProduct(
180
175
  _ sku:String,
181
176
  andDangerouslyFinishTransactionAutomatically: Bool,
177
+ applicationUsername:String?,
182
178
  resolve: @escaping RCTPromiseResolveBlock = { _ in },
183
179
  reject: @escaping RCTPromiseRejectBlock = { _, _, _ in }
184
180
  ) {
@@ -197,6 +193,10 @@ class RNIapIos: RCTEventEmitter, SKRequestDelegate, SKPaymentTransactionObserver
197
193
  addPromise(forKey: prod.productIdentifier, resolve: resolve, reject: reject)
198
194
 
199
195
  let payment = SKMutablePayment(product: prod)
196
+
197
+ if (applicationUsername != nil) {
198
+ payment.applicationUsername = applicationUsername
199
+ }
200
200
  SKPaymentQueue.default().add(payment)
201
201
  } else{
202
202
  if hasListeners {
@@ -341,7 +341,7 @@ class RNIapIos: RCTEventEmitter, SKRequestDelegate, SKPaymentTransactionObserver
341
341
  reject: @escaping RCTPromiseRejectBlock = { _, _, _ in }
342
342
  ) {
343
343
  print("\n\n\n *** get promoted product. \n\n.")
344
- resolve(promotedProduct )
344
+ resolve((promotedProduct != nil) ? getProductObject(promotedProduct!) : nil)
345
345
  }
346
346
 
347
347
  @objc public func buyPromotedProduct(
package/package.json CHANGED
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "name": "react-native-iap",
3
- "version": "8.2.2",
4
- "packageManager": "yarn@3.2.0",
3
+ "version": "8.5.0",
5
4
  "description": "React Native In App Purchase Module.",
6
5
  "main": "index.js",
7
6
  "types": "index.d.ts",
@@ -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>;
@@ -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;