react-native-iap 8.2.2 → 9.0.0-beta

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.
@@ -9,10 +9,13 @@ import com.android.billingclient.api.BillingFlowParams.SubscriptionUpdateParams
9
9
  import com.android.billingclient.api.BillingResult
10
10
  import com.android.billingclient.api.ConsumeParams
11
11
  import com.android.billingclient.api.ConsumeResponseListener
12
+ import com.android.billingclient.api.ProductDetails
12
13
  import com.android.billingclient.api.Purchase
14
+ import com.android.billingclient.api.PurchaseHistoryRecord
13
15
  import com.android.billingclient.api.PurchasesUpdatedListener
14
- import com.android.billingclient.api.SkuDetails
15
- import com.android.billingclient.api.SkuDetailsParams
16
+ import com.android.billingclient.api.QueryProductDetailsParams
17
+ import com.android.billingclient.api.QueryPurchaseHistoryParams
18
+ import com.android.billingclient.api.QueryPurchasesParams
16
19
  import com.facebook.react.bridge.Arguments
17
20
  import com.facebook.react.bridge.LifecycleEventListener
18
21
  import com.facebook.react.bridge.Promise
@@ -22,13 +25,13 @@ import com.facebook.react.bridge.ReactContext
22
25
  import com.facebook.react.bridge.ReactContextBaseJavaModule
23
26
  import com.facebook.react.bridge.ReactMethod
24
27
  import com.facebook.react.bridge.ReadableArray
28
+ import com.facebook.react.bridge.ReadableType
25
29
  import com.facebook.react.bridge.WritableMap
26
30
  import com.facebook.react.bridge.WritableNativeArray
27
31
  import com.facebook.react.bridge.WritableNativeMap
28
32
  import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter
29
33
  import com.google.android.gms.common.ConnectionResult
30
34
  import com.google.android.gms.common.GoogleApiAvailability
31
- import java.math.BigDecimal
32
35
  import java.util.ArrayList
33
36
 
34
37
  class RNIapModule(
@@ -37,23 +40,33 @@ class RNIapModule(
37
40
  private val googleApiAvailability: GoogleApiAvailability = GoogleApiAvailability.getInstance()
38
41
  ) :
39
42
  ReactContextBaseJavaModule(reactContext),
40
- PurchasesUpdatedListener {
43
+ PurchasesUpdatedListener,
44
+ RNIapModuleInterface {
41
45
 
42
- private var billingClient: BillingClient = builder.setListener(this).build()
43
- private val skus: MutableMap<String, SkuDetails> = mutableMapOf()
46
+ private var billingClientCache: BillingClient? = null
47
+ private val skus: MutableMap<String, ProductDetails> = mutableMapOf()
44
48
  override fun getName(): String {
45
49
  return TAG
46
50
  }
47
51
 
48
- internal fun ensureConnection(promise: Promise, callback: () -> Unit) {
49
- if (billingClient.isReady) {
50
- callback()
52
+ internal fun ensureConnection(
53
+ promise: Promise,
54
+ callback: (billingClient: BillingClient) -> Unit
55
+ ) {
56
+ val billingClient = billingClientCache
57
+ if (billingClient?.isReady == true) {
58
+ callback(billingClient)
51
59
  return
52
60
  } else {
53
61
  val nested = PromiseImpl(
54
62
  {
55
63
  if (it.isNotEmpty() && it[0] is Boolean && it[0] as Boolean) {
56
- callback()
64
+ val connectedBillingClient = billingClientCache
65
+ if (connectedBillingClient?.isReady == true) {
66
+ callback(connectedBillingClient)
67
+ } else {
68
+ promise.safeReject(DoobooUtils.E_NOT_PREPARED, "Unable to auto-initialize connection")
69
+ }
57
70
  } else {
58
71
  Log.i(TAG, "Incorrect parameter in resolve")
59
72
  }
@@ -61,7 +74,8 @@ class RNIapModule(
61
74
  {
62
75
  if (it.size > 1 && it[0] is String && it[1] is String) {
63
76
  promise.safeReject(
64
- it[0] as String, it[1] as String
77
+ it[0] as String,
78
+ it[1] as String
65
79
  )
66
80
  } else {
67
81
  Log.i(TAG, "Incorrect parameters in reject")
@@ -73,15 +87,7 @@ class RNIapModule(
73
87
  }
74
88
 
75
89
  @ReactMethod
76
- 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
- }
90
+ override fun initConnection(promise: Promise) {
85
91
  if (googleApiAvailability.isGooglePlayServicesAvailable(reactContext)
86
92
  != ConnectionResult.SUCCESS
87
93
  ) {
@@ -90,25 +96,36 @@ class RNIapModule(
90
96
  return
91
97
  }
92
98
 
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
99
+ if (billingClientCache?.isReady == true) {
100
+ Log.i(
101
+ TAG,
102
+ "Already initialized, you should only call initConnection() once when your app starts"
103
+ )
104
+ promise.safeResolve(true)
105
+ return
106
+ }
107
+ builder.setListener(this).build().also {
108
+ billingClientCache = it
109
+ it.startConnection(
110
+ object : BillingClientStateListener {
111
+ override fun onBillingSetupFinished(billingResult: BillingResult) {
112
+ if (!isValidResult(billingResult, promise)) return
99
113
 
100
- promise.safeResolve(true)
101
- }
114
+ promise.safeResolve(true)
115
+ }
102
116
 
103
- override fun onBillingServiceDisconnected() {
104
- Log.i(TAG, "Billing service disconnected")
117
+ override fun onBillingServiceDisconnected() {
118
+ Log.i(TAG, "Billing service disconnected")
119
+ }
105
120
  }
106
- })
121
+ )
122
+ }
107
123
  }
108
124
 
109
125
  @ReactMethod
110
- fun endConnection(promise: Promise) {
111
- billingClient.endConnection()
126
+ override fun endConnection(promise: Promise) {
127
+ billingClientCache?.endConnection()
128
+ billingClientCache = null
112
129
  promise.safeResolve(true)
113
130
  }
114
131
 
@@ -120,7 +137,7 @@ class RNIapModule(
120
137
  for (purchase in purchases) {
121
138
  ensureConnection(
122
139
  promise
123
- ) {
140
+ ) { billingClient ->
124
141
  val consumeParams =
125
142
  ConsumeParams.newBuilder().setPurchaseToken(purchase.purchaseToken)
126
143
  .build()
@@ -143,12 +160,14 @@ class RNIapModule(
143
160
  }
144
161
 
145
162
  @ReactMethod
146
- fun flushFailedPurchasesCachedAsPending(promise: Promise) {
163
+ override fun flushFailedPurchasesCachedAsPending(promise: Promise) {
147
164
  ensureConnection(
148
165
  promise
149
- ) {
166
+ ) { billingClient ->
150
167
  billingClient.queryPurchasesAsync(
151
- BillingClient.SkuType.INAPP
168
+ QueryPurchasesParams.newBuilder().setProductType(
169
+ BillingClient.ProductType.INAPP
170
+ ).build()
152
171
  ) { billingResult: BillingResult, list: List<Purchase>? ->
153
172
  if (!isValidResult(billingResult, promise)) return@queryPurchasesAsync
154
173
  if (list == null) {
@@ -174,77 +193,93 @@ class RNIapModule(
174
193
  }
175
194
 
176
195
  @ReactMethod
177
- fun getItemsByType(type: String, skuArr: ReadableArray, promise: Promise) {
196
+ override fun getItemsByType(type: String, skuArr: ReadableArray, promise: Promise) {
178
197
  ensureConnection(
179
198
  promise
180
- ) {
181
- val skuList = ArrayList<String>()
199
+ ) { billingClient ->
200
+ val skuList = ArrayList<QueryProductDetailsParams.Product>()
182
201
  for (i in 0 until skuArr.size()) {
183
- val sku = skuArr.getString(i)
184
- if (sku is String) {
185
- skuList.add(sku)
202
+ if (skuArr.getType(i) == ReadableType.String) {
203
+ val sku = skuArr.getString(i)
204
+ skuList.add(
205
+ QueryProductDetailsParams.Product.newBuilder().setProductId(sku)
206
+ .setProductType(type).build()
207
+ )
186
208
  }
187
209
  }
188
- val params = SkuDetailsParams.newBuilder()
189
- params.setSkusList(skuList).setType(type)
190
- billingClient.querySkuDetailsAsync(
210
+ val params = QueryProductDetailsParams.newBuilder().setProductList(skuList)
211
+ billingClient.queryProductDetailsAsync(
191
212
  params.build()
192
- ) { billingResult: BillingResult, skuDetailsList: List<SkuDetails>? ->
193
- if (!isValidResult(billingResult, promise)) return@querySkuDetailsAsync
213
+ ) { billingResult: BillingResult, skuDetailsList: List<ProductDetails> ->
214
+ if (!isValidResult(billingResult, promise)) return@queryProductDetailsAsync
194
215
 
195
- if (skuDetailsList != null) {
196
- for (sku in skuDetailsList) {
197
- skus[sku.sku] = sku
198
- }
199
- }
200
216
  val items = Arguments.createArray()
201
- for (skuDetails in skuDetailsList!!) {
217
+ for (skuDetails in skuDetailsList) {
218
+ skus[skuDetails.productId] = skuDetails
219
+
202
220
  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)
221
+ item.putString("productId", skuDetails.productId)
220
222
  item.putString("title", skuDetails.title)
221
223
  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)
224
+ item.putString("productType", skuDetails.productType)
225
+ item.putString("name", skuDetails.name)
226
+ val oneTimePurchaseOfferDetails = Arguments.createMap()
227
+ skuDetails.oneTimePurchaseOfferDetails?.let {
228
+ oneTimePurchaseOfferDetails.putString(
229
+ "priceCurrencyCode",
230
+ it.priceCurrencyCode
231
+ )
232
+ oneTimePurchaseOfferDetails.putString("formattedPrice", it.formattedPrice)
233
+ oneTimePurchaseOfferDetails.putString(
234
+ "priceAmountMicros",
235
+ it.priceAmountMicros.toString()
236
+ )
237
+ }
238
+ item.putMap("oneTimePurchaseOfferDetails", oneTimePurchaseOfferDetails)
239
+
240
+ val subscriptionOfferDetails = Arguments.createArray()
241
+ skuDetails.subscriptionOfferDetails?.forEach { subscriptionOfferDetailsItem ->
242
+ val offerDetails = Arguments.createMap()
243
+ offerDetails.putString(
244
+ "offerToken",
245
+ subscriptionOfferDetailsItem.offerToken
246
+ )
247
+ val offerTags = Arguments.createArray()
248
+ subscriptionOfferDetailsItem.offerTags.forEach { offerTag ->
249
+ offerTags.pushString(offerTag)
250
+ }
251
+ offerDetails.putArray("offerTags", offerTags)
252
+
253
+ val pricingPhasesList = Arguments.createArray()
254
+ subscriptionOfferDetailsItem.pricingPhases.pricingPhaseList.forEach { pricingPhaseItem ->
255
+ val pricingPhase = Arguments.createMap()
256
+ pricingPhase.putString(
257
+ "formattedPrice",
258
+ pricingPhaseItem.formattedPrice
259
+ )
260
+ pricingPhase.putString(
261
+ "priceCurrencyCode",
262
+ pricingPhaseItem.priceCurrencyCode
263
+ )
264
+ pricingPhase.putString("billingPeriod", pricingPhaseItem.billingPeriod)
265
+ pricingPhase.putInt(
266
+ "billingCycleCount",
267
+ pricingPhaseItem.billingCycleCount
268
+ )
269
+ pricingPhase.putString(
270
+ "priceAmountMicros",
271
+ pricingPhaseItem.priceAmountMicros.toString()
272
+ )
273
+ pricingPhase.putInt("recurrenceMode", pricingPhaseItem.recurrenceMode)
274
+
275
+ pricingPhasesList.pushMap(pricingPhase)
276
+ }
277
+ val pricingPhases = Arguments.createMap()
278
+ pricingPhases.putArray("pricingPhaseList", pricingPhasesList)
279
+ offerDetails.putMap("pricingPhases", pricingPhases)
280
+ subscriptionOfferDetails.pushMap(offerDetails)
281
+ }
282
+ item.putArray("subscriptionOfferDetails", subscriptionOfferDetails)
248
283
  items.pushMap(item)
249
284
  }
250
285
  promise.safeResolve(items)
@@ -269,20 +304,22 @@ class RNIapModule(
269
304
  }
270
305
 
271
306
  @ReactMethod
272
- fun getAvailableItemsByType(type: String, promise: Promise) {
307
+ override fun getAvailableItemsByType(type: String, promise: Promise) {
273
308
  ensureConnection(
274
309
  promise
275
- ) {
310
+ ) { billingClient ->
276
311
  val items = WritableNativeArray()
277
312
  billingClient.queryPurchasesAsync(
278
- if (type == "subs") BillingClient.SkuType.SUBS else BillingClient.SkuType.INAPP
313
+ QueryPurchasesParams.newBuilder().setProductType(
314
+ if (type == "subs") BillingClient.ProductType.SUBS else BillingClient.ProductType.INAPP
315
+ ).build()
279
316
  ) { billingResult: BillingResult, purchases: List<Purchase>? ->
280
317
  if (!isValidResult(billingResult, promise)) return@queryPurchasesAsync
281
318
  if (purchases != null) {
282
319
  for (i in purchases.indices) {
283
320
  val purchase = purchases[i]
284
321
  val item = WritableNativeMap()
285
- item.putString("productId", purchase.skus[0])
322
+ item.putString("productId", purchase.products[0]) // TODO: should be a list
286
323
  item.putString("transactionId", purchase.orderId)
287
324
  item.putDouble("transactionDate", purchase.purchaseTime.toDouble())
288
325
  item.putString("transactionReceipt", purchase.originalJson)
@@ -295,13 +332,13 @@ class RNIapModule(
295
332
  item.putString("packageNameAndroid", purchase.packageName)
296
333
  item.putString(
297
334
  "obfuscatedAccountIdAndroid",
298
- purchase.accountIdentifiers!!.obfuscatedAccountId
335
+ purchase.accountIdentifiers?.obfuscatedAccountId
299
336
  )
300
337
  item.putString(
301
338
  "obfuscatedProfileIdAndroid",
302
- purchase.accountIdentifiers!!.obfuscatedProfileId
339
+ purchase.accountIdentifiers?.obfuscatedProfileId
303
340
  )
304
- if (type == BillingClient.SkuType.SUBS) {
341
+ if (type == BillingClient.ProductType.SUBS) {
305
342
  item.putBoolean("autoRenewingAndroid", purchase.isAutoRenewing)
306
343
  }
307
344
  items.pushMap(item)
@@ -313,20 +350,24 @@ class RNIapModule(
313
350
  }
314
351
 
315
352
  @ReactMethod
316
- fun getPurchaseHistoryByType(type: String, promise: Promise) {
353
+ override fun getPurchaseHistoryByType(type: String, promise: Promise) {
317
354
  ensureConnection(
318
355
  promise
319
- ) {
356
+ ) { billingClient ->
320
357
  billingClient.queryPurchaseHistoryAsync(
321
- if (type == "subs") BillingClient.SkuType.SUBS else BillingClient.SkuType.INAPP
322
- ) { billingResult, purchaseHistoryRecordList ->
358
+ QueryPurchaseHistoryParams.newBuilder().setProductType(
359
+ if (type == "subs") BillingClient.ProductType.SUBS else BillingClient.ProductType.INAPP
360
+ ).build()
361
+ ) {
362
+ billingResult: BillingResult, purchaseHistoryRecordList: MutableList<PurchaseHistoryRecord>? ->
363
+
323
364
  if (!isValidResult(billingResult, promise)) return@queryPurchaseHistoryAsync
324
365
 
325
366
  Log.d(TAG, purchaseHistoryRecordList.toString())
326
367
  val items = Arguments.createArray()
327
368
  purchaseHistoryRecordList?.forEach { purchase ->
328
369
  val item = Arguments.createMap()
329
- item.putString("productId", purchase.skus[0])
370
+ item.putString("productId", purchase.products[0])
330
371
  item.putDouble("transactionDate", purchase.purchaseTime.toDouble())
331
372
  item.putString("transactionReceipt", purchase.originalJson)
332
373
  item.putString("purchaseToken", purchase.purchaseToken)
@@ -341,13 +382,14 @@ class RNIapModule(
341
382
  }
342
383
 
343
384
  @ReactMethod
344
- fun buyItemByType(
385
+ override fun buyItemByType(
345
386
  type: String,
346
- sku: String,
387
+ sku: String, // TODO: should this now be an array?
347
388
  purchaseToken: String?,
348
389
  prorationMode: Int?,
349
390
  obfuscatedAccountId: String?,
350
391
  obfuscatedProfileId: String?,
392
+ selectedOfferIndex: Int?, // New optional parameter in V5, TODO: should it be an array?
351
393
  promise: Promise
352
394
  ) {
353
395
  val activity = currentActivity
@@ -357,12 +399,13 @@ class RNIapModule(
357
399
  }
358
400
  ensureConnection(
359
401
  promise
360
- ) {
402
+ ) { billingClient ->
361
403
  DoobooUtils.instance.addPromiseForKey(
362
- PROMISE_BUY_ITEM, promise
404
+ PROMISE_BUY_ITEM,
405
+ promise
363
406
  )
364
407
  val builder = BillingFlowParams.newBuilder()
365
- val selectedSku: SkuDetails? = skus[sku]
408
+ val selectedSku: ProductDetails? = skus[sku]
366
409
  if (selectedSku == null) {
367
410
  val debugMessage =
368
411
  "The sku was not found. Please fetch products first by calling getItems"
@@ -375,10 +418,21 @@ class RNIapModule(
375
418
  promise.safeReject(PROMISE_BUY_ITEM, debugMessage)
376
419
  return@ensureConnection
377
420
  }
378
- builder.setSkuDetails(selectedSku)
421
+ var productParams = BillingFlowParams.ProductDetailsParams.newBuilder().setProductDetails(selectedSku)
422
+ if (selectedOfferIndex != null && (
423
+ selectedSku.subscriptionOfferDetails?.size
424
+ ?: 0
425
+ ) > selectedOfferIndex
426
+ ) {
427
+ val offerToken =
428
+ selectedSku.subscriptionOfferDetails?.get(selectedOfferIndex)?.offerToken
429
+ offerToken?.let { productParams = productParams.setOfferToken(offerToken) }
430
+ }
431
+
432
+ builder.setProductDetailsParamsList(listOf(productParams.build()))
379
433
  val subscriptionUpdateParamsBuilder = SubscriptionUpdateParams.newBuilder()
380
434
  if (purchaseToken != null) {
381
- subscriptionUpdateParamsBuilder.setOldSkuPurchaseToken(purchaseToken)
435
+ subscriptionUpdateParamsBuilder.setOldPurchaseToken(purchaseToken)
382
436
  }
383
437
  if (obfuscatedAccountId != null) {
384
438
  builder.setObfuscatedAccountId(obfuscatedAccountId)
@@ -390,7 +444,7 @@ class RNIapModule(
390
444
  if (prorationMode
391
445
  == BillingFlowParams.ProrationMode.IMMEDIATE_AND_CHARGE_PRORATED_PRICE
392
446
  ) {
393
- subscriptionUpdateParamsBuilder.setReplaceSkusProrationMode(
447
+ subscriptionUpdateParamsBuilder.setReplaceProrationMode(
394
448
  BillingFlowParams.ProrationMode.IMMEDIATE_AND_CHARGE_PRORATED_PRICE
395
449
  )
396
450
  if (type != BillingClient.SkuType.SUBS) {
@@ -411,27 +465,27 @@ class RNIapModule(
411
465
  } else if (prorationMode
412
466
  == BillingFlowParams.ProrationMode.IMMEDIATE_WITHOUT_PRORATION
413
467
  ) {
414
- subscriptionUpdateParamsBuilder.setReplaceSkusProrationMode(
468
+ subscriptionUpdateParamsBuilder.setReplaceProrationMode(
415
469
  BillingFlowParams.ProrationMode.IMMEDIATE_WITHOUT_PRORATION
416
470
  )
417
471
  } else if (prorationMode == BillingFlowParams.ProrationMode.DEFERRED) {
418
- subscriptionUpdateParamsBuilder.setReplaceSkusProrationMode(
472
+ subscriptionUpdateParamsBuilder.setReplaceProrationMode(
419
473
  BillingFlowParams.ProrationMode.DEFERRED
420
474
  )
421
475
  } else if (prorationMode
422
476
  == BillingFlowParams.ProrationMode.IMMEDIATE_WITH_TIME_PRORATION
423
477
  ) {
424
- subscriptionUpdateParamsBuilder.setReplaceSkusProrationMode(
478
+ subscriptionUpdateParamsBuilder.setReplaceProrationMode(
425
479
  BillingFlowParams.ProrationMode.IMMEDIATE_WITHOUT_PRORATION
426
480
  )
427
481
  } else if (prorationMode
428
482
  == BillingFlowParams.ProrationMode.IMMEDIATE_AND_CHARGE_FULL_PRICE
429
483
  ) {
430
- subscriptionUpdateParamsBuilder.setReplaceSkusProrationMode(
484
+ subscriptionUpdateParamsBuilder.setReplaceProrationMode(
431
485
  BillingFlowParams.ProrationMode.IMMEDIATE_AND_CHARGE_FULL_PRICE
432
486
  )
433
487
  } else {
434
- subscriptionUpdateParamsBuilder.setReplaceSkusProrationMode(
488
+ subscriptionUpdateParamsBuilder.setReplaceProrationMode(
435
489
  BillingFlowParams.ProrationMode.UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY
436
490
  )
437
491
  }
@@ -441,30 +495,30 @@ class RNIapModule(
441
495
  builder.setSubscriptionUpdateParams(subscriptionUpdateParams)
442
496
  }
443
497
  val flowParams = builder.build()
444
- val billingResult = billingClient.launchBillingFlow(activity, flowParams)
445
- if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
498
+ val billingResultCode = billingClient.launchBillingFlow(activity, flowParams).responseCode
499
+ if (billingResultCode == BillingClient.BillingResponseCode.OK) {
446
500
  promise.safeResolve(true)
447
501
  return@ensureConnection
448
502
  } else {
449
503
  val errorData: Array<String?> =
450
- PlayUtils.instance.getBillingResponseData(billingResult.responseCode)
504
+ PlayUtils.instance.getBillingResponseData(billingResultCode)
451
505
  promise.safeReject(errorData[0], errorData[1])
452
506
  }
453
507
  }
454
508
  }
455
509
 
456
510
  @ReactMethod
457
- fun acknowledgePurchase(
458
- token: String?,
511
+ override fun acknowledgePurchase(
512
+ token: String,
459
513
  developerPayLoad: String?,
460
514
  promise: Promise
461
515
  ) {
462
516
  ensureConnection(
463
517
  promise
464
- ) {
518
+ ) { billingClient ->
465
519
  val acknowledgePurchaseParams =
466
520
  AcknowledgePurchaseParams.newBuilder().setPurchaseToken(
467
- token!!
521
+ token
468
522
  ).build()
469
523
  billingClient.acknowledgePurchase(
470
524
  acknowledgePurchaseParams
@@ -484,15 +538,15 @@ class RNIapModule(
484
538
  }
485
539
 
486
540
  @ReactMethod
487
- fun consumeProduct(
488
- token: String?,
541
+ override fun consumeProduct(
542
+ token: String,
489
543
  developerPayLoad: String?,
490
544
  promise: Promise
491
545
  ) {
492
- val params = ConsumeParams.newBuilder().setPurchaseToken(token!!).build()
546
+ val params = ConsumeParams.newBuilder().setPurchaseToken(token).build()
493
547
  ensureConnection(
494
548
  promise
495
- ) {
549
+ ) { billingClient ->
496
550
  billingClient.consumeAsync(
497
551
  params
498
552
  ) { billingResult: BillingResult, purchaseToken: String? ->
@@ -529,7 +583,7 @@ class RNIapModule(
529
583
  for (i in purchases.indices) {
530
584
  val item = Arguments.createMap()
531
585
  val purchase = purchases[i]
532
- item.putString("productId", purchase.skus[0])
586
+ item.putString("productId", purchase.products[0])
533
587
  item.putString("transactionId", purchase.orderId)
534
588
  item.putDouble("transactionDate", purchase.purchaseTime.toDouble())
535
589
  item.putString("transactionReceipt", purchase.originalJson)
@@ -576,11 +630,13 @@ class RNIapModule(
576
630
  private fun sendUnconsumedPurchases(promise: Promise) {
577
631
  ensureConnection(
578
632
  promise
579
- ) {
580
- val types = arrayOf(BillingClient.SkuType.INAPP, BillingClient.SkuType.SUBS)
633
+ ) { billingClient ->
634
+ val types = arrayOf(BillingClient.ProductType.INAPP, BillingClient.ProductType.SUBS)
581
635
  for (type in types) {
582
636
  billingClient.queryPurchasesAsync(
583
- type
637
+ QueryPurchasesParams.newBuilder().setProductType(
638
+ type
639
+ ).build()
584
640
  ) { billingResult: BillingResult, list: List<Purchase> ->
585
641
  if (!isValidResult(billingResult, promise)) return@queryPurchasesAsync
586
642
 
@@ -593,23 +649,22 @@ class RNIapModule(
593
649
  }
594
650
 
595
651
  @ReactMethod
596
- fun startListening(promise: Promise) {
652
+ override fun startListening(promise: Promise) {
597
653
  sendUnconsumedPurchases(promise)
598
654
  }
599
655
 
600
656
  @ReactMethod
601
- fun addListener(eventName: String) {
657
+ override fun addListener(eventName: String) {
602
658
  // Keep: Required for RN built-in Event Emitter Calls.
603
659
  }
604
660
 
605
661
  @ReactMethod
606
- fun removeListeners(count: Double) {
662
+ override fun removeListeners(count: Double) {
607
663
  // Keep: Required for RN built-in Event Emitter Calls.
608
664
  }
609
665
 
610
- @get:ReactMethod
611
- val packageName: String
612
- get() = reactApplicationContext.packageName
666
+ @ReactMethod
667
+ override fun getPackageName(promise: Promise) = promise.resolve(reactApplicationContext.packageName)
613
668
 
614
669
  private fun sendEvent(
615
670
  reactContext: ReactContext,
@@ -631,7 +686,7 @@ class RNIapModule(
631
686
  override fun onHostResume() {}
632
687
  override fun onHostPause() {}
633
688
  override fun onHostDestroy() {
634
- billingClient.endConnection()
689
+ billingClientCache?.endConnection()
635
690
  }
636
691
  }
637
692
  reactContext.addLifecycleEventListener(lifecycleEventListener)
@@ -0,0 +1,44 @@
1
+ package com.dooboolab.RNIap
2
+
3
+ import com.android.billingclient.api.BillingResult
4
+ import com.android.billingclient.api.Purchase
5
+ import com.facebook.react.bridge.Promise
6
+ import com.facebook.react.bridge.ReadableArray
7
+
8
+ /**
9
+ * Common interface for consistency
10
+ */
11
+ interface RNIapModuleInterface {
12
+ fun getName(): String
13
+ fun initConnection(promise: Promise)
14
+ fun endConnection(promise: Promise)
15
+ fun flushFailedPurchasesCachedAsPending(promise: Promise)
16
+ fun getItemsByType(type: String, skuArr: ReadableArray, promise: Promise)
17
+ fun getAvailableItemsByType(type: String, promise: Promise)
18
+ fun getPurchaseHistoryByType(type: String, promise: Promise)
19
+ fun buyItemByType(
20
+ type: String,
21
+ sku: String,
22
+ purchaseToken: String?,
23
+ prorationMode: Int?,
24
+ obfuscatedAccountId: String?,
25
+ obfuscatedProfileId: String?,
26
+ selectedOfferIndex: Int?, // New optional parameter in V5 (added to maintain interface consistency)
27
+ promise: Promise
28
+ )
29
+ fun acknowledgePurchase(
30
+ token: String,
31
+ developerPayLoad: String?,
32
+ promise: Promise
33
+ )
34
+ fun consumeProduct(
35
+ token: String,
36
+ developerPayLoad: String?,
37
+ promise: Promise
38
+ )
39
+ fun onPurchasesUpdated(billingResult: BillingResult, purchases: List<Purchase>?)
40
+ fun startListening(promise: Promise)
41
+ fun addListener(eventName: String)
42
+ fun removeListeners(count: Double)
43
+ fun getPackageName(promise: Promise)
44
+ }