react-native-iap 14.3.5-rc.1 → 14.3.5

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.
Files changed (79) hide show
  1. package/NitroIap.podspec +1 -1
  2. package/README.md +1 -1
  3. package/android/CMakeLists.txt +4 -0
  4. package/android/build.gradle +2 -2
  5. package/android/src/main/cpp/cpp-adapter.cpp +8 -0
  6. package/android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt +93 -51
  7. package/ios/HybridRnIap.swift +38 -30
  8. package/lib/module/hooks/useIAP.js +3 -5
  9. package/lib/module/hooks/useIAP.js.map +1 -1
  10. package/lib/module/index.js +54 -55
  11. package/lib/module/index.js.map +1 -1
  12. package/lib/module/types.js +1 -1
  13. package/lib/module/utils/type-bridge.js.map +1 -1
  14. package/lib/typescript/src/hooks/useIAP.d.ts +4 -4
  15. package/lib/typescript/src/hooks/useIAP.d.ts.map +1 -1
  16. package/lib/typescript/src/index.d.ts +8 -7
  17. package/lib/typescript/src/index.d.ts.map +1 -1
  18. package/lib/typescript/src/specs/RnIap.nitro.d.ts +2 -1
  19. package/lib/typescript/src/specs/RnIap.nitro.d.ts.map +1 -1
  20. package/lib/typescript/src/types.d.ts +141 -146
  21. package/lib/typescript/src/types.d.ts.map +1 -1
  22. package/lib/typescript/src/utils/type-bridge.d.ts.map +1 -1
  23. package/nitrogen/generated/android/NitroIap+autolinking.cmake +9 -4
  24. package/nitrogen/generated/android/c++/JHybridRnIapSpec.cpp +32 -5
  25. package/nitrogen/generated/android/c++/JHybridRnIapSpec.hpp +1 -1
  26. package/nitrogen/generated/android/c++/JIapPlatform.hpp +59 -0
  27. package/nitrogen/generated/android/c++/JPurchase.cpp +26 -0
  28. package/nitrogen/generated/android/c++/JPurchase.hpp +80 -0
  29. package/nitrogen/generated/android/c++/JPurchaseAndroid.hpp +140 -0
  30. package/nitrogen/generated/android/c++/JPurchaseIOS.hpp +194 -0
  31. package/nitrogen/generated/android/c++/JPurchaseOfferIOS.hpp +61 -0
  32. package/nitrogen/generated/android/c++/JPurchaseState.hpp +71 -0
  33. package/nitrogen/generated/android/c++/JRequestPurchaseResult.hpp +89 -0
  34. package/nitrogen/generated/android/c++/JVariant_PurchaseAndroid_PurchaseIOS.cpp +26 -0
  35. package/nitrogen/generated/android/c++/JVariant_PurchaseAndroid_PurchaseIOS.hpp +80 -0
  36. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/HybridRnIapSpec.kt +1 -1
  37. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/IapPlatform.kt +21 -0
  38. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/Purchase.kt +42 -0
  39. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/PurchaseAndroid.kt +77 -0
  40. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/PurchaseIOS.kt +116 -0
  41. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/PurchaseOfferIOS.kt +35 -0
  42. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/PurchaseState.kt +25 -0
  43. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/RequestPurchaseResult.kt +32 -0
  44. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/Variant_PurchaseAndroid_PurchaseIOS.kt +42 -0
  45. package/nitrogen/generated/ios/NitroIap-Swift-Cxx-Bridge.cpp +13 -5
  46. package/nitrogen/generated/ios/NitroIap-Swift-Cxx-Bridge.hpp +186 -25
  47. package/nitrogen/generated/ios/NitroIap-Swift-Cxx-Umbrella.hpp +18 -0
  48. package/nitrogen/generated/ios/c++/HybridRnIapSpecSwift.hpp +20 -2
  49. package/nitrogen/generated/ios/swift/Func_void_RequestPurchaseResult.swift +47 -0
  50. package/nitrogen/generated/ios/swift/HybridRnIapSpec.swift +1 -1
  51. package/nitrogen/generated/ios/swift/HybridRnIapSpec_cxx.swift +7 -7
  52. package/nitrogen/generated/ios/swift/IapPlatform.swift +40 -0
  53. package/nitrogen/generated/ios/swift/Purchase.swift +18 -0
  54. package/nitrogen/generated/ios/swift/PurchaseAndroid.swift +399 -0
  55. package/nitrogen/generated/ios/swift/PurchaseIOS.swift +768 -0
  56. package/nitrogen/generated/ios/swift/PurchaseOfferIOS.swift +57 -0
  57. package/nitrogen/generated/ios/swift/PurchaseState.swift +56 -0
  58. package/nitrogen/generated/ios/swift/RequestPurchaseResult.swift +148 -0
  59. package/nitrogen/generated/ios/swift/Variant_PurchaseAndroid_PurchaseIOS.swift +18 -0
  60. package/nitrogen/generated/shared/c++/HybridRnIapSpec.hpp +4 -1
  61. package/nitrogen/generated/shared/c++/IapPlatform.hpp +76 -0
  62. package/nitrogen/generated/shared/c++/PurchaseAndroid.hpp +138 -0
  63. package/nitrogen/generated/shared/c++/PurchaseIOS.hpp +193 -0
  64. package/nitrogen/generated/shared/c++/PurchaseOfferIOS.hpp +75 -0
  65. package/nitrogen/generated/shared/c++/PurchaseState.hpp +92 -0
  66. package/nitrogen/generated/shared/c++/RequestPurchaseResult.hpp +78 -0
  67. package/package.json +5 -4
  68. package/plugin/build/withIAP.js +1 -1
  69. package/plugin/src/withIAP.ts +1 -1
  70. package/plugin/tsconfig.tsbuildinfo +1 -1
  71. package/src/hooks/useIAP.ts +13 -11
  72. package/src/index.ts +73 -77
  73. package/src/specs/RnIap.nitro.ts +4 -1
  74. package/src/types.ts +168 -178
  75. package/src/utils/type-bridge.ts +3 -1
  76. package/lib/index.d.ts +0 -8
  77. package/lib/index.js +0 -36
  78. package/lib/specs/RnIap.nitro.d.ts +0 -7
  79. package/lib/specs/RnIap.nitro.js +0 -1
package/NitroIap.podspec CHANGED
@@ -32,7 +32,7 @@ Pod::Spec.new do |s|
32
32
  s.dependency 'React-jsi'
33
33
  s.dependency 'React-callinvoker'
34
34
  # OpenIAP Apple for StoreKit 2 integration
35
- s.dependency 'openiap', '1.1.9'
35
+ s.dependency 'openiap', '1.1.12'
36
36
 
37
37
  install_modules_dependencies(s)
38
38
  end
package/README.md CHANGED
@@ -94,7 +94,7 @@ Add the OpenIAP Google library to your `android/app/build.gradle` dependencies:
94
94
 
95
95
  ```gradle
96
96
  dependencies {
97
- implementation "io.github.hyochan.openiap:openiap-google:1.1.0"
97
+ implementation "io.github.hyochan.openiap:openiap-google:1.1.10"
98
98
  }
99
99
  ```
100
100
 
@@ -13,6 +13,10 @@ add_library(${PACKAGE_NAME} SHARED
13
13
  src/main/cpp/cpp-adapter.cpp
14
14
  )
15
15
 
16
+ # Ensure Android/iOS preprocessor macros from the NDK do not clash with
17
+ # the generated enum values in Nitrogen headers (IapPlatform.hpp).
18
+ target_compile_options(${PACKAGE_NAME} PRIVATE -UANDROID -UIOS)
19
+
16
20
  # Add Nitrogen specs :)
17
21
  include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/NitroIap+autolinking.cmake)
18
22
 
@@ -156,8 +156,8 @@ dependencies {
156
156
  // Kotlin coroutines
157
157
  implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0'
158
158
 
159
- // OpenIAP Google (v1.1.0)
160
- implementation 'io.github.hyochan.openiap:openiap-google:1.1.0'
159
+ // OpenIAP Google (v1.1.10)
160
+ implementation 'io.github.hyochan.openiap:openiap-google:1.1.10'
161
161
  }
162
162
 
163
163
  configurations.all {
@@ -1,4 +1,12 @@
1
1
  #include <jni.h>
2
+
3
+ #ifdef ANDROID
4
+ #undef ANDROID
5
+ #endif
6
+ #ifdef IOS
7
+ #undef IOS
8
+ #endif
9
+
2
10
  #include "NitroIapOnLoad.hpp"
3
11
 
4
12
  JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) {
@@ -4,15 +4,15 @@ import android.util.Log
4
4
  import com.facebook.react.bridge.ReactApplicationContext
5
5
  import com.margelo.nitro.NitroModules
6
6
  import com.margelo.nitro.core.Promise
7
- import dev.hyo.openiap.OpenIapError
7
+ import dev.hyo.openiap.OpenIapError as OpenIAPError
8
8
  import dev.hyo.openiap.OpenIapModule
9
9
  import dev.hyo.openiap.listener.OpenIapPurchaseErrorListener
10
10
  import dev.hyo.openiap.listener.OpenIapPurchaseUpdateListener
11
+ import dev.hyo.openiap.models.DeepLinkOptions
11
12
  import dev.hyo.openiap.models.OpenIapProduct
12
13
  import dev.hyo.openiap.models.OpenIapPurchase
13
- import dev.hyo.openiap.models.DeepLinkOptions
14
14
  import dev.hyo.openiap.models.ProductRequest
15
- import dev.hyo.openiap.models.RequestPurchaseAndroidProps
15
+ import dev.hyo.openiap.models.OpenIapRequestPurchaseProps
16
16
  import dev.hyo.openiap.models.OpenIapSerialization
17
17
  import kotlinx.coroutines.Dispatchers
18
18
  import kotlinx.coroutines.withContext
@@ -68,8 +68,8 @@ class HybridRnIap : HybridRnIapSpec() {
68
68
  .onFailure { Log.e(TAG, "Failed to forward purchase update", it) }
69
69
  })
70
70
  openIap.addPurchaseErrorListener(OpenIapPurchaseErrorListener { e ->
71
- val code = OpenIapError.toCode(e)
72
- val message = e.message ?: OpenIapError.defaultMessage(code)
71
+ val code = OpenIAPError.toCode(e)
72
+ val message = e.message ?: OpenIAPError.defaultMessage(code)
73
73
  runCatching {
74
74
  sendPurchaseError(
75
75
  NitroPurchaseResult(
@@ -88,12 +88,23 @@ class HybridRnIap : HybridRnIapSpec() {
88
88
  val deferred = initDeferred!!
89
89
  try {
90
90
  val ok = runCatching { openIap.initConnection() }.getOrElse { err ->
91
- val error = OpenIapError.InitConnection(err.message ?: "Failed to initialize connection")
92
- throw Exception(toErrorJson(error))
91
+ val error = OpenIAPError.InitConnection()
92
+ throw Exception(
93
+ toErrorJson(
94
+ error = error,
95
+ debugMessage = err.message,
96
+ messageOverride = err.message
97
+ )
98
+ )
93
99
  }
94
100
  if (!ok) {
95
- val error = OpenIapError.InitConnection("Failed to initialize connection")
96
- throw Exception(toErrorJson(error))
101
+ val error = OpenIAPError.InitConnection()
102
+ throw Exception(
103
+ toErrorJson(
104
+ error = error,
105
+ messageOverride = "Failed to initialize connection"
106
+ )
107
+ )
97
108
  }
98
109
  isInitialized = true
99
110
  deferred.complete(true)
@@ -125,7 +136,7 @@ class HybridRnIap : HybridRnIapSpec() {
125
136
  Log.d(TAG, "fetchProducts (OpenIAP) skus=${skus.joinToString()} type=$type")
126
137
 
127
138
  if (skus.isEmpty()) {
128
- throw Exception(toErrorJson(OpenIapError.EmptySkuList))
139
+ throw Exception(toErrorJson(OpenIAPError.EmptySkuList))
129
140
  }
130
141
 
131
142
  initConnection().await()
@@ -141,17 +152,19 @@ class HybridRnIap : HybridRnIapSpec() {
141
152
 
142
153
  // Purchase methods
143
154
  // Purchase methods (Unified)
144
- override fun requestPurchase(request: NitroPurchaseRequest): Promise<Unit> {
155
+ override fun requestPurchase(request: NitroPurchaseRequest): Promise<RequestPurchaseResult> {
145
156
  return Promise.async {
157
+ val defaultResult = RequestPurchaseResult(null, null)
158
+
146
159
  val androidRequest = request.android ?: run {
147
160
  // Programming error: no Android params provided
148
- sendPurchaseError(toErrorResult(OpenIapError.DeveloperError))
149
- return@async
161
+ sendPurchaseError(toErrorResult(OpenIAPError.DeveloperError))
162
+ return@async defaultResult
150
163
  }
151
164
 
152
165
  if (androidRequest.skus.isEmpty()) {
153
- sendPurchaseError(toErrorResult(OpenIapError.EmptySkuList))
154
- return@async
166
+ sendPurchaseError(toErrorResult(OpenIAPError.EmptySkuList))
167
+ return@async defaultResult
155
168
  }
156
169
 
157
170
  try {
@@ -160,14 +173,14 @@ class HybridRnIap : HybridRnIapSpec() {
160
173
 
161
174
  val missing = androidRequest.skus.firstOrNull { !productTypeBySku.containsKey(it) }
162
175
  if (missing != null) {
163
- sendPurchaseError(toErrorResult(OpenIapError.SkuNotFound(missing), missing))
164
- return@async
176
+ sendPurchaseError(toErrorResult(OpenIAPError.SkuNotFound(missing), missing))
177
+ return@async defaultResult
165
178
  }
166
179
  val typeStr = androidRequest.skus.firstOrNull()?.let { productTypeBySku[it] } ?: "inapp"
167
180
  val typeEnum = ProductRequest.ProductRequestType.fromString(typeStr)
168
181
 
169
182
  val result = openIap.requestPurchase(
170
- RequestPurchaseAndroidProps(
183
+ OpenIapRequestPurchaseProps(
171
184
  skus = androidRequest.skus.toList(),
172
185
  obfuscatedAccountIdAndroid = androidRequest.obfuscatedAccountIdAndroid,
173
186
  obfuscatedProfileIdAndroid = androidRequest.obfuscatedProfileIdAndroid,
@@ -180,8 +193,17 @@ class HybridRnIap : HybridRnIapSpec() {
180
193
  runCatching { sendPurchaseUpdate(convertToNitroPurchase(p)) }
181
194
  .onFailure { Log.e(TAG, "Failed to forward PURCHASE_UPDATED", it) }
182
195
  }
196
+
197
+ defaultResult
183
198
  } catch (e: Exception) {
184
- sendPurchaseError(toErrorResult(OpenIapError.PurchaseFailed(e.message ?: "Purchase failed")))
199
+ sendPurchaseError(
200
+ toErrorResult(
201
+ error = OpenIAPError.PurchaseFailed(),
202
+ debugMessage = e.message,
203
+ messageOverride = e.message
204
+ )
205
+ )
206
+ defaultResult
185
207
  }
186
208
  }
187
209
  }
@@ -215,7 +237,7 @@ class HybridRnIap : HybridRnIapSpec() {
215
237
  NitroPurchaseResult(
216
238
  responseCode = -1.0,
217
239
  debugMessage = "Missing purchaseToken",
218
- code = OpenIapError.toCode(OpenIapError.DeveloperError),
240
+ code = OpenIAPError.toCode(OpenIAPError.DeveloperError),
219
241
  message = "Missing purchaseToken",
220
242
  purchaseToken = null
221
243
  )
@@ -226,13 +248,13 @@ class HybridRnIap : HybridRnIapSpec() {
226
248
  try {
227
249
  initConnection().await()
228
250
  } catch (e: Exception) {
229
- val err = OpenIapError.InitConnection(e.message ?: "Failed to initialize connection")
251
+ val err = OpenIAPError.InitConnection()
230
252
  return@async Variant_Boolean_NitroPurchaseResult.Second(
231
253
  NitroPurchaseResult(
232
254
  responseCode = -1.0,
233
255
  debugMessage = e.message,
234
- code = OpenIapError.toCode(err),
235
- message = err.message,
256
+ code = OpenIAPError.toCode(err),
257
+ message = e.message?.takeIf { it.isNotBlank() } ?: err.message,
236
258
  purchaseToken = purchaseToken
237
259
  )
238
260
  )
@@ -254,14 +276,14 @@ class HybridRnIap : HybridRnIapSpec() {
254
276
  )
255
277
  )
256
278
  } catch (e: Exception) {
257
- val err = OpenIapError.BillingError(e.message ?: "Service error")
279
+ val err = OpenIAPError.BillingError()
258
280
  Variant_Boolean_NitroPurchaseResult.Second(
259
281
  NitroPurchaseResult(
260
282
  responseCode = -1.0,
261
283
  debugMessage = e.message,
262
- code = OpenIapError.toCode(err),
263
- message = err.message,
264
- purchaseToken = purchaseToken
284
+ code = OpenIAPError.toCode(err),
285
+ message = e.message?.takeIf { it.isNotBlank() } ?: err.message,
286
+ purchaseToken = null
265
287
  )
266
288
  )
267
289
  }
@@ -356,7 +378,7 @@ class HybridRnIap : HybridRnIapSpec() {
356
378
  var subscriptionPeriodAndroid: String? = null
357
379
  var freeTrialPeriodAndroid: String? = null
358
380
 
359
- if (product.type == OpenIapProduct.ProductType.INAPP) {
381
+ if (product.type == OpenIapProduct.ProductType.InApp) {
360
382
  product.oneTimePurchaseOfferDetailsAndroid?.let { otp ->
361
383
  originalPriceAndroid = otp.formattedPrice
362
384
  // priceAmountMicros is a string; parse to number if possible
@@ -428,8 +450,8 @@ class HybridRnIap : HybridRnIapSpec() {
428
450
  private fun convertToNitroPurchase(purchase: OpenIapPurchase): NitroPurchase {
429
451
  // Map OpenIAP purchase state back to legacy numeric Android state for compatibility
430
452
  val purchaseStateAndroidNumeric = when (purchase.purchaseState) {
431
- OpenIapPurchase.PurchaseState.PURCHASED -> 1.0
432
- OpenIapPurchase.PurchaseState.PENDING -> 2.0
453
+ OpenIapPurchase.PurchaseState.Purchased -> 1.0
454
+ OpenIapPurchase.PurchaseState.Pending -> 2.0
433
455
  else -> 0.0 // UNSPECIFIED/UNKNOWN/other
434
456
  }
435
457
  return NitroPurchase(
@@ -465,14 +487,14 @@ class HybridRnIap : HybridRnIapSpec() {
465
487
  // iOS-specific method - not supported on Android
466
488
  override fun getStorefrontIOS(): Promise<String> {
467
489
  return Promise.async {
468
- throw Exception(toErrorJson(OpenIapError.NotSupported))
490
+ throw Exception(toErrorJson(OpenIAPError.NotSupported))
469
491
  }
470
492
  }
471
493
 
472
494
  // iOS-specific method - not supported on Android
473
495
  override fun getAppTransactionIOS(): Promise<String?> {
474
496
  return Promise.async {
475
- throw Exception(toErrorJson(OpenIapError.NotSupported))
497
+ throw Exception(toErrorJson(OpenIAPError.NotSupported))
476
498
  }
477
499
  }
478
500
 
@@ -558,7 +580,7 @@ class HybridRnIap : HybridRnIapSpec() {
558
580
  try {
559
581
  // For Android, we need the androidOptions to be provided
560
582
  val androidOptions = params.androidOptions
561
- ?: throw Exception(toErrorJson(OpenIapError.DeveloperError))
583
+ ?: throw Exception(toErrorJson(OpenIAPError.DeveloperError))
562
584
 
563
585
  // Android receipt validation would typically involve server-side validation
564
586
  // using Google Play Developer API. Here we provide a simplified implementation
@@ -595,7 +617,15 @@ class HybridRnIap : HybridRnIapSpec() {
595
617
  Variant_NitroReceiptValidationResultIOS_NitroReceiptValidationResultAndroid.Second(result)
596
618
 
597
619
  } catch (e: Exception) {
598
- throw Exception(toErrorJson(OpenIapError.InvalidReceipt("Receipt validation failed: ${e.message}")))
620
+ val debugMessage = e.message
621
+ val error = OpenIAPError.InvalidReceipt()
622
+ throw Exception(
623
+ toErrorJson(
624
+ error = error,
625
+ debugMessage = debugMessage,
626
+ messageOverride = "Receipt validation failed: ${debugMessage ?: "unknown reason"}"
627
+ )
628
+ )
599
629
  }
600
630
  }
601
631
  }
@@ -603,31 +633,31 @@ class HybridRnIap : HybridRnIapSpec() {
603
633
  // iOS-specific methods - Not applicable on Android, return appropriate defaults
604
634
  override fun subscriptionStatusIOS(sku: String): Promise<Array<NitroSubscriptionStatus>?> {
605
635
  return Promise.async {
606
- throw Exception(toErrorJson(OpenIapError.NotSupported))
636
+ throw Exception(toErrorJson(OpenIAPError.NotSupported))
607
637
  }
608
638
  }
609
639
 
610
640
  override fun currentEntitlementIOS(sku: String): Promise<NitroPurchase?> {
611
641
  return Promise.async {
612
- throw Exception(toErrorJson(OpenIapError.NotSupported))
642
+ throw Exception(toErrorJson(OpenIAPError.NotSupported))
613
643
  }
614
644
  }
615
645
 
616
646
  override fun latestTransactionIOS(sku: String): Promise<NitroPurchase?> {
617
647
  return Promise.async {
618
- throw Exception(toErrorJson(OpenIapError.NotSupported))
648
+ throw Exception(toErrorJson(OpenIAPError.NotSupported))
619
649
  }
620
650
  }
621
651
 
622
652
  override fun getPendingTransactionsIOS(): Promise<Array<NitroPurchase>> {
623
653
  return Promise.async {
624
- throw Exception(toErrorJson(OpenIapError.NotSupported))
654
+ throw Exception(toErrorJson(OpenIAPError.NotSupported))
625
655
  }
626
656
  }
627
657
 
628
658
  override fun syncIOS(): Promise<Boolean> {
629
659
  return Promise.async {
630
- throw Exception(toErrorJson(OpenIapError.NotSupported))
660
+ throw Exception(toErrorJson(OpenIAPError.NotSupported))
631
661
  }
632
662
  }
633
663
 
@@ -635,49 +665,61 @@ class HybridRnIap : HybridRnIapSpec() {
635
665
 
636
666
  override fun isEligibleForIntroOfferIOS(groupID: String): Promise<Boolean> {
637
667
  return Promise.async {
638
- throw Exception(toErrorJson(OpenIapError.NotSupported))
668
+ throw Exception(toErrorJson(OpenIAPError.NotSupported))
639
669
  }
640
670
  }
641
671
 
642
672
  override fun getReceiptDataIOS(): Promise<String> {
643
673
  return Promise.async {
644
- throw Exception(toErrorJson(OpenIapError.NotSupported))
674
+ throw Exception(toErrorJson(OpenIAPError.NotSupported))
645
675
  }
646
676
  }
647
677
 
648
678
  override fun isTransactionVerifiedIOS(sku: String): Promise<Boolean> {
649
679
  return Promise.async {
650
- throw Exception(toErrorJson(OpenIapError.NotSupported))
680
+ throw Exception(toErrorJson(OpenIAPError.NotSupported))
651
681
  }
652
682
  }
653
683
 
654
684
  override fun getTransactionJwsIOS(sku: String): Promise<String?> {
655
685
  return Promise.async {
656
- throw Exception(toErrorJson(OpenIapError.NotSupported))
686
+ throw Exception(toErrorJson(OpenIAPError.NotSupported))
657
687
  }
658
688
  }
659
689
 
660
690
  // ---------------------------------------------------------------------
661
691
  // OpenIAP error helpers: unify error codes/messages from library
662
692
  // ---------------------------------------------------------------------
663
- private fun toErrorJson(error: OpenIapError, productId: String? = null): String {
664
- val code = OpenIapError.toCode(error)
665
- val message = error.message.ifEmpty { OpenIapError.defaultMessage(code) }
693
+ private fun toErrorJson(
694
+ error: OpenIAPError,
695
+ productId: String? = null,
696
+ debugMessage: String? = null,
697
+ messageOverride: String? = null
698
+ ): String {
699
+ val code = OpenIAPError.toCode(error)
700
+ val message = messageOverride?.takeIf { it.isNotBlank() }
701
+ ?: error.message.ifEmpty { OpenIAPError.defaultMessage(code) }
666
702
  return BillingUtils.createErrorJson(
667
703
  code = code,
668
704
  message = message,
669
705
  responseCode = -1,
670
- debugMessage = error.message,
706
+ debugMessage = debugMessage ?: error.message,
671
707
  productId = productId
672
708
  )
673
709
  }
674
710
 
675
- private fun toErrorResult(error: OpenIapError, productId: String? = null): NitroPurchaseResult {
676
- val code = OpenIapError.toCode(error)
677
- val message = error.message.ifEmpty { OpenIapError.defaultMessage(code) }
711
+ private fun toErrorResult(
712
+ error: OpenIAPError,
713
+ productId: String? = null,
714
+ debugMessage: String? = null,
715
+ messageOverride: String? = null
716
+ ): NitroPurchaseResult {
717
+ val code = OpenIAPError.toCode(error)
718
+ val message = messageOverride?.takeIf { it.isNotBlank() }
719
+ ?: error.message.ifEmpty { OpenIAPError.defaultMessage(code) }
678
720
  return NitroPurchaseResult(
679
721
  responseCode = -1.0,
680
- debugMessage = error.message,
722
+ debugMessage = debugMessage ?: error.message,
681
723
  code = code,
682
724
  message = message,
683
725
  purchaseToken = null
@@ -164,7 +164,7 @@ class HybridRnIap: HybridRnIapSpec {
164
164
  } catch {
165
165
  // Surface as event and keep flags consistent
166
166
  let err = self.createPurchaseErrorResult(
167
- code: OpenIapError.E_INIT_CONNECTION,
167
+ code: OpenIapError.InitConnection,
168
168
  message: error.localizedDescription,
169
169
  productId: nil
170
170
  )
@@ -198,29 +198,30 @@ class HybridRnIap: HybridRnIapSpec {
198
198
  }
199
199
  }
200
200
 
201
- func requestPurchase(request: NitroPurchaseRequest) throws -> Promise<Void> {
201
+ func requestPurchase(request: NitroPurchaseRequest) throws -> Promise<RequestPurchaseResult> {
202
202
  return Promise.async {
203
+ let defaultResult = RequestPurchaseResult(purchase: nil, purchases: nil)
203
204
  guard let iosRequest = request.ios else {
204
205
  let error = self.createPurchaseErrorResult(
205
- code: OpenIapError.E_USER_ERROR,
206
+ code: OpenIapError.UserError,
206
207
  message: "No iOS request provided"
207
208
  )
208
209
  self.sendPurchaseError(error, productId: nil)
209
- return
210
+ return defaultResult
210
211
  }
211
212
  do {
212
213
  // Event-first behavior: don't reject Promise on connection issues
213
214
  guard self.isInitialized else {
214
215
  #if DEBUG
215
- print("[HybridRnIap] requestPurchase while not initialized; sending E_INIT_CONNECTION")
216
+ print("[HybridRnIap] requestPurchase while not initialized; sending InitConnection")
216
217
  #endif
217
218
  let err = self.createPurchaseErrorResult(
218
- code: OpenIapError.E_INIT_CONNECTION,
219
+ code: OpenIapError.InitConnection,
219
220
  message: "IAP store connection not initialized",
220
221
  productId: iosRequest.sku
221
222
  )
222
223
  self.sendPurchaseError(err, productId: iosRequest.sku)
223
- return
224
+ return defaultResult
224
225
  }
225
226
  // Delegate purchase to OpenIAP. It emits success/error events which we bridge above.
226
227
  let props = OpenIapRequestPurchaseProps(
@@ -238,15 +239,17 @@ class HybridRnIap: HybridRnIapSpec {
238
239
  }
239
240
  )
240
241
  _ = try await OpenIapModule.shared.requestPurchase(props)
242
+ return defaultResult
241
243
  } catch {
242
244
  // Ensure an error reaches JS even if OpenIAP threw before emitting.
243
245
  // Use simple de-duplication window to avoid double-emitting.
244
246
  let err = self.createPurchaseErrorResult(
245
- code: OpenIapError.E_SERVICE_ERROR,
247
+ code: OpenIapError.ServiceError,
246
248
  message: error.localizedDescription,
247
249
  productId: iosRequest.sku
248
250
  )
249
251
  self.sendPurchaseErrorDedup(err, productId: iosRequest.sku)
252
+ return defaultResult
250
253
  }
251
254
  }
252
255
  }
@@ -261,7 +264,7 @@ class HybridRnIap: HybridRnIapSpec {
261
264
  return purchases.map { self.convertOpenIapPurchaseToNitroPurchase($0) }
262
265
  } catch {
263
266
  // Propagate OpenIAP error or map to network error
264
- throw OpenIapError.make(code: OpenIapError.E_NETWORK_ERROR)
267
+ throw OpenIapError.make(code: OpenIapError.NetworkError)
265
268
  }
266
269
  }
267
270
  }
@@ -275,7 +278,7 @@ class HybridRnIap: HybridRnIapSpec {
275
278
  return .first(ok)
276
279
  } catch {
277
280
  let tid = iosParams.transactionId
278
- throw OpenIapError.make(code: OpenIapError.E_PURCHASE_ERROR, message: "Transaction not found: \(tid)")
281
+ throw OpenIapError.make(code: OpenIapError.PurchaseError, message: "Transaction not found: \(tid)")
279
282
  }
280
283
  }
281
284
  }
@@ -296,7 +299,7 @@ class HybridRnIap: HybridRnIapSpec {
296
299
  )
297
300
  return .first(mapped)
298
301
  } catch {
299
- throw OpenIapError.make(code: OpenIapError.E_RECEIPT_FAILED, message: error.localizedDescription)
302
+ throw OpenIapError.make(code: OpenIapError.ReceiptFailed, message: error.localizedDescription)
300
303
  }
301
304
  }
302
305
  }
@@ -308,7 +311,7 @@ class HybridRnIap: HybridRnIapSpec {
308
311
  do {
309
312
  return try await OpenIapModule.shared.getStorefrontIOS()
310
313
  } catch {
311
- throw OpenIapError.make(code: OpenIapError.E_SERVICE_ERROR, message: error.localizedDescription)
314
+ throw OpenIapError.make(code: OpenIapError.ServiceError, message: error.localizedDescription)
312
315
  }
313
316
  }
314
317
  }
@@ -384,7 +387,7 @@ class HybridRnIap: HybridRnIapSpec {
384
387
  return ok
385
388
  } catch {
386
389
  // Fallback with explicit error for simulator or unsupported cases
387
- throw OpenIapError.make(code: OpenIapError.E_FEATURE_NOT_SUPPORTED)
390
+ throw OpenIapError.make(code: OpenIapError.FeatureNotSupported)
388
391
  }
389
392
  }
390
393
  }
@@ -443,12 +446,13 @@ class HybridRnIap: HybridRnIapSpec {
443
446
  return Promise.async {
444
447
  try self.ensureConnection()
445
448
  do {
446
- if let p = try await OpenIapModule.shared.currentEntitlementIOS(sku: sku) {
447
- return self.convertOpenIapPurchaseToNitroPurchase(p)
449
+ let purchase = try await OpenIapModule.shared.currentEntitlementIOS(sku: sku)
450
+ if let purchase {
451
+ return self.convertOpenIapPurchaseToNitroPurchase(purchase)
448
452
  }
449
- return nil
453
+ return Optional<NitroPurchase>.none
450
454
  } catch {
451
- throw OpenIapError.make(code: OpenIapError.E_SKU_NOT_FOUND, productId: sku)
455
+ throw OpenIapError.make(code: OpenIapError.SkuNotFound, productId: sku)
452
456
  }
453
457
  }
454
458
  }
@@ -457,12 +461,13 @@ class HybridRnIap: HybridRnIapSpec {
457
461
  return Promise.async {
458
462
  try self.ensureConnection()
459
463
  do {
460
- if let p = try await OpenIapModule.shared.latestTransactionIOS(sku: sku) {
461
- return self.convertOpenIapPurchaseToNitroPurchase(p)
464
+ let purchase = try await OpenIapModule.shared.latestTransactionIOS(sku: sku)
465
+ if let purchase {
466
+ return self.convertOpenIapPurchaseToNitroPurchase(purchase)
462
467
  }
463
- return nil
468
+ return Optional<NitroPurchase>.none
464
469
  } catch {
465
- throw OpenIapError.make(code: OpenIapError.E_SKU_NOT_FOUND, productId: sku)
470
+ throw OpenIapError.make(code: OpenIapError.SkuNotFound, productId: sku)
466
471
  }
467
472
  }
468
473
  }
@@ -484,7 +489,7 @@ class HybridRnIap: HybridRnIapSpec {
484
489
  let ok = try await OpenIapModule.shared.syncIOS()
485
490
  return ok
486
491
  } catch {
487
- throw OpenIapError.make(code: OpenIapError.E_SERVICE_ERROR, message: error.localizedDescription)
492
+ throw OpenIapError.make(code: OpenIapError.ServiceError, message: error.localizedDescription)
488
493
  }
489
494
  }
490
495
  }
@@ -498,7 +503,7 @@ class HybridRnIap: HybridRnIapSpec {
498
503
  let purchases = try await OpenIapModule.shared.getAvailablePurchases(OpenIapPurchaseOptions(alsoPublishToEventListenerIOS: false, onlyIncludeActiveItemsIOS: true))
499
504
  return purchases.map { self.convertOpenIapPurchaseToNitroPurchase($0) }
500
505
  } catch {
501
- throw OpenIapError.make(code: OpenIapError.E_SERVICE_ERROR, message: error.localizedDescription)
506
+ throw OpenIapError.make(code: OpenIapError.ServiceError, message: error.localizedDescription)
502
507
  }
503
508
  }
504
509
  }
@@ -515,9 +520,9 @@ class HybridRnIap: HybridRnIapSpec {
515
520
  if let receipt = try await OpenIapModule.shared.getReceiptDataIOS() {
516
521
  return receipt
517
522
  }
518
- throw OpenIapError.make(code: OpenIapError.E_RECEIPT_FAILED)
523
+ throw OpenIapError.make(code: OpenIapError.ReceiptFailed)
519
524
  } catch {
520
- throw OpenIapError.make(code: OpenIapError.E_RECEIPT_FAILED, message: error.localizedDescription)
525
+ throw OpenIapError.make(code: OpenIapError.ReceiptFailed, message: error.localizedDescription)
521
526
  }
522
527
  }
523
528
  }
@@ -532,8 +537,11 @@ class HybridRnIap: HybridRnIapSpec {
532
537
  func getTransactionJwsIOS(sku: String) throws -> Promise<String?> {
533
538
  return Promise.async {
534
539
  try self.ensureConnection()
535
- do { return try await OpenIapModule.shared.getTransactionJwsIOS(sku: sku) } catch {
536
- throw OpenIapError.make(code: OpenIapError.E_TRANSACTION_VALIDATION_FAILED, message: "Can't find transaction for sku \(sku)")
540
+ do {
541
+ let jws = try await OpenIapModule.shared.getTransactionJwsIOS(sku: sku)
542
+ return jws
543
+ } catch {
544
+ throw OpenIapError.make(code: OpenIapError.TransactionValidationFailed, message: "Can't find transaction for sku \(sku)")
537
545
  }
538
546
  }
539
547
  }
@@ -602,7 +610,7 @@ class HybridRnIap: HybridRnIapSpec {
602
610
 
603
611
  private func ensureConnection() throws {
604
612
  guard isInitialized else {
605
- throw OpenIapError.make(code: OpenIapError.E_INIT_CONNECTION, message: "Connection not initialized. Call initConnection() first.")
613
+ throw OpenIapError.make(code: OpenIapError.InitConnection, message: "Connection not initialized. Call initConnection() first.")
606
614
  }
607
615
  }
608
616
 
@@ -720,13 +728,13 @@ class HybridRnIap: HybridRnIapSpec {
720
728
  // because the TS spec marks them as Android-only.
721
729
  func getStorefrontAndroid() throws -> Promise<String> {
722
730
  return Promise.async {
723
- throw OpenIapError.make(code: OpenIapError.E_FEATURE_NOT_SUPPORTED)
731
+ throw OpenIapError.make(code: OpenIapError.FeatureNotSupported)
724
732
  }
725
733
  }
726
734
 
727
735
  func deepLinkToSubscriptionsAndroid(options: NitroDeepLinkOptionsAndroid) throws -> Promise<Void> {
728
736
  return Promise.async {
729
- throw OpenIapError.make(code: OpenIapError.E_FEATURE_NOT_SUPPORTED)
737
+ throw OpenIapError.make(code: OpenIapError.FeatureNotSupported)
730
738
  }
731
739
  }
732
740
  }
@@ -13,8 +13,6 @@ import { normalizeErrorCodeFromNative } from "../utils/errorMapping.js";
13
13
 
14
14
  // Types for event subscriptions
15
15
 
16
- const PRODUCT_QUERY_TYPE_IN_APP = 'in-app';
17
- const PRODUCT_QUERY_TYPE_SUBS = 'subs';
18
16
  /**
19
17
  * React Hook for managing In-App Purchases.
20
18
  * See documentation at https://react-native-iap.hyo.dev/docs/hooks/useIAP
@@ -65,7 +63,7 @@ export function useIAP(options) {
65
63
  try {
66
64
  const result = await fetchProducts({
67
65
  skus,
68
- type: PRODUCT_QUERY_TYPE_IN_APP
66
+ type: 'in-app'
69
67
  });
70
68
  setProducts(prevProducts => mergeWithDuplicateCheck(prevProducts, result, product => product.id));
71
69
  } catch (error) {
@@ -76,7 +74,7 @@ export function useIAP(options) {
76
74
  try {
77
75
  const result = await fetchProducts({
78
76
  skus,
79
- type: PRODUCT_QUERY_TYPE_SUBS
77
+ type: 'subs'
80
78
  });
81
79
  setSubscriptions(prevSubscriptions => mergeWithDuplicateCheck(prevSubscriptions, result, subscription => subscription.id));
82
80
  } catch (error) {
@@ -90,7 +88,7 @@ export function useIAP(options) {
90
88
  }
91
89
  try {
92
90
  const result = await fetchProducts(params);
93
- if (params.type === PRODUCT_QUERY_TYPE_SUBS) {
91
+ if (params.type === 'subs') {
94
92
  setSubscriptions(prevSubscriptions => mergeWithDuplicateCheck(prevSubscriptions, result, subscription => subscription.id));
95
93
  } else {
96
94
  setProducts(prevProducts => mergeWithDuplicateCheck(prevProducts, result, product => product.id));