react-native-iap 16.4.0 → 16.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/android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt +200 -142
- package/android/src/main/java/com/margelo/nitro/iap/TokenizedListenerRegistry.kt +0 -1
- package/ios/HybridRnIap.swift +832 -165
- package/ios/RnIapHelper.swift +1 -1
- package/lib/module/hooks/useIAP.js +48 -32
- package/lib/module/hooks/useIAP.js.map +1 -1
- package/lib/module/index.js +206 -152
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.kepler.js +1 -0
- package/lib/module/index.kepler.js.map +1 -1
- package/lib/module/types.js +1 -0
- package/lib/module/types.js.map +1 -1
- package/lib/module/utils/error.js +5 -4
- package/lib/module/utils/error.js.map +1 -1
- package/lib/module/vega-adapter.js +4 -1
- package/lib/module/vega-adapter.js.map +1 -1
- package/lib/typescript/src/hooks/useIAP.d.ts +31 -15
- package/lib/typescript/src/hooks/useIAP.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +28 -14
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/index.kepler.d.ts.map +1 -1
- package/lib/typescript/src/specs/RnIap.nitro.d.ts +10 -0
- package/lib/typescript/src/specs/RnIap.nitro.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +11 -2
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/utils/error.d.ts.map +1 -1
- package/lib/typescript/src/vega-adapter.d.ts.map +1 -1
- package/nitrogen/generated/android/NitroIap+autolinking.cmake +1 -0
- package/nitrogen/generated/android/c++/JHybridRnIapSpec.cpp +9 -0
- package/nitrogen/generated/android/c++/JNitroPurchaseRequest.hpp +7 -1
- package/nitrogen/generated/android/c++/JNitroPurchaseRequestType.hpp +58 -0
- package/nitrogen/generated/android/c++/JNitroVerifyPurchaseWithIapkitHorizonProps.hpp +66 -0
- package/nitrogen/generated/android/c++/JNitroVerifyPurchaseWithIapkitProps.hpp +8 -1
- package/nitrogen/generated/android/c++/JNitroVerifyPurchaseWithProviderProps.hpp +3 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_NitroVerifyPurchaseWithIapkitHorizonProps.cpp +26 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_NitroVerifyPurchaseWithIapkitHorizonProps.hpp +74 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_NitroVerifyPurchaseWithIapkitProps.hpp +3 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroPurchaseRequest.kt +8 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroPurchaseRequestType.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroVerifyPurchaseWithIapkitHorizonProps.kt +56 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroVerifyPurchaseWithIapkitProps.kt +7 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/Variant_NullType_NitroVerifyPurchaseWithIapkitHorizonProps.kt +62 -0
- package/nitrogen/generated/ios/NitroIap-Swift-Cxx-Bridge.hpp +65 -0
- package/nitrogen/generated/ios/NitroIap-Swift-Cxx-Umbrella.hpp +6 -0
- package/nitrogen/generated/ios/c++/HybridRnIapSpecSwift.hpp +6 -0
- package/nitrogen/generated/ios/swift/NitroPurchaseRequest.swift +13 -2
- package/nitrogen/generated/ios/swift/NitroPurchaseRequestType.swift +40 -0
- package/nitrogen/generated/ios/swift/NitroVerifyPurchaseWithIapkitHorizonProps.swift +66 -0
- package/nitrogen/generated/ios/swift/NitroVerifyPurchaseWithIapkitProps.swift +38 -1
- package/nitrogen/generated/ios/swift/Variant_NullType_NitroVerifyPurchaseWithIapkitHorizonProps.swift +30 -0
- package/nitrogen/generated/shared/c++/NitroPurchaseRequest.hpp +9 -2
- package/nitrogen/generated/shared/c++/NitroPurchaseRequestType.hpp +76 -0
- package/nitrogen/generated/shared/c++/NitroVerifyPurchaseWithIapkitHorizonProps.hpp +90 -0
- package/nitrogen/generated/shared/c++/NitroVerifyPurchaseWithIapkitProps.hpp +8 -1
- package/openiap-versions.json +3 -3
- package/package.json +1 -1
- package/src/hooks/useIAP.ts +96 -58
- package/src/index.kepler.ts +1 -0
- package/src/index.ts +248 -195
- package/src/specs/RnIap.nitro.ts +12 -0
- package/src/types.ts +12 -2
- package/src/utils/error.ts +7 -3
- package/src/vega-adapter.ts +9 -5
|
@@ -78,13 +78,53 @@ class OpenIapException(private val errorJson: String, cause: Throwable? = null)
|
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
internal fun parseOpenIapError(err: Throwable): OpenIapError {
|
|
82
|
+
var cause: Throwable? = err
|
|
83
|
+
while (cause != null) {
|
|
84
|
+
if (cause is OpenIapError) return cause
|
|
85
|
+
cause = cause.cause
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
cause = err
|
|
89
|
+
while (cause != null) {
|
|
90
|
+
val message = cause.message ?: ""
|
|
91
|
+
when {
|
|
92
|
+
message.contains("not prepared", ignoreCase = true) ||
|
|
93
|
+
message.contains("not initialized", ignoreCase = true) -> return OpenIapError.NotPrepared
|
|
94
|
+
message.contains("developer error", ignoreCase = true) ||
|
|
95
|
+
message.contains("activity not available", ignoreCase = true) -> return OpenIapError.DeveloperError()
|
|
96
|
+
message.contains("network", ignoreCase = true) -> return OpenIapError.NetworkError
|
|
97
|
+
message.contains("service unavailable", ignoreCase = true) ||
|
|
98
|
+
message.contains("billing unavailable", ignoreCase = true) -> return OpenIapError.ServiceUnavailable()
|
|
99
|
+
}
|
|
100
|
+
cause = cause.cause
|
|
101
|
+
}
|
|
102
|
+
return OpenIapError.ServiceUnavailable()
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
internal fun normalizeAvailablePurchasesType(type: NitroAvailablePurchasesAndroidType?): String? =
|
|
106
|
+
when (type) {
|
|
107
|
+
NitroAvailablePurchasesAndroidType.IN_APP -> "in-app"
|
|
108
|
+
NitroAvailablePurchasesAndroidType.SUBS -> "subs"
|
|
109
|
+
null -> null
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
internal fun rejectDisconnectedPurchase(
|
|
113
|
+
isInitialized: Boolean,
|
|
114
|
+
sendError: (OpenIapError) -> Unit,
|
|
115
|
+
): Boolean {
|
|
116
|
+
if (isInitialized) return false
|
|
117
|
+
sendError(OpenIapError.NotPrepared)
|
|
118
|
+
return true
|
|
119
|
+
}
|
|
120
|
+
|
|
81
121
|
internal suspend fun endRnConnectionWithCleanup(
|
|
82
122
|
endConnection: suspend () -> Boolean,
|
|
83
123
|
cleanup: () -> Unit,
|
|
84
|
-
): Boolean
|
|
85
|
-
endConnection()
|
|
86
|
-
} finally {
|
|
124
|
+
): Boolean {
|
|
125
|
+
val result = endConnection()
|
|
87
126
|
cleanup()
|
|
127
|
+
return result
|
|
88
128
|
}
|
|
89
129
|
|
|
90
130
|
/**
|
|
@@ -139,6 +179,17 @@ internal fun requireMatchingPurchaseOptions(
|
|
|
139
179
|
}
|
|
140
180
|
}
|
|
141
181
|
|
|
182
|
+
internal inline fun <T> catchNonCancellation(
|
|
183
|
+
block: () -> T,
|
|
184
|
+
onFailure: (Exception) -> T,
|
|
185
|
+
): T = try {
|
|
186
|
+
block()
|
|
187
|
+
} catch (error: CancellationException) {
|
|
188
|
+
throw error
|
|
189
|
+
} catch (error: Exception) {
|
|
190
|
+
onFailure(error)
|
|
191
|
+
}
|
|
192
|
+
|
|
142
193
|
/**
|
|
143
194
|
* Bound for the pending purchase-event queues, matching expo-iap's
|
|
144
195
|
* ExpoIapHelper.MAX_BUFFERED_EVENTS. Oldest entries are dropped on overflow.
|
|
@@ -183,6 +234,7 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
183
234
|
private val developerProvidedBillingListenersAndroid = mutableListOf<(DeveloperProvidedBillingDetailsAndroid) -> Unit>()
|
|
184
235
|
private val subscriptionBillingIssueListeners = mutableListOf<(NitroPurchase) -> Unit>()
|
|
185
236
|
private var listenersAttached = false
|
|
237
|
+
@Volatile
|
|
186
238
|
private var isInitialized = false
|
|
187
239
|
private val connectionLifecycleQueue = ConnectionLifecycleQueue()
|
|
188
240
|
|
|
@@ -206,15 +258,10 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
206
258
|
private fun Variant_NullType_Double?.unwrapDouble(): Double? = (this as? Variant_NullType_Double.Second)?.value
|
|
207
259
|
private fun Variant_NullType_Boolean?.unwrapBool(): Boolean? = (this as? Variant_NullType_Boolean.Second)?.value
|
|
208
260
|
|
|
209
|
-
private suspend fun ensureConnection() {
|
|
210
|
-
initConnection(null as Variant_NullType_InitConnectionConfig?).await()
|
|
211
|
-
}
|
|
212
|
-
|
|
213
261
|
override fun initConnection(config: Variant_NullType_InitConnectionConfig?): Promise<Boolean> {
|
|
214
262
|
val configValue = (config as? Variant_NullType_InitConnectionConfig.Second)?.value
|
|
215
263
|
val performInit: suspend () -> Boolean = initOperation@{
|
|
216
264
|
RnIapLog.payload("initConnection", configValue)
|
|
217
|
-
if (isInitialized) return@initOperation true
|
|
218
265
|
|
|
219
266
|
// CRITICAL: Set Activity BEFORE calling initConnection
|
|
220
267
|
// Horizon SDK needs Activity to initialize OVRPlatform with proper returnComponent
|
|
@@ -236,7 +283,7 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
236
283
|
}
|
|
237
284
|
} catch (err: CancellationException) {
|
|
238
285
|
throw err
|
|
239
|
-
} catch (err:
|
|
286
|
+
} catch (err: Exception) {
|
|
240
287
|
val error = OpenIapError.InitConnection
|
|
241
288
|
val errorMessage = err.message ?: err.javaClass.name
|
|
242
289
|
RnIapLog.failure("initConnection.setActivity", err)
|
|
@@ -337,7 +384,7 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
337
384
|
}
|
|
338
385
|
} catch (err: CancellationException) {
|
|
339
386
|
throw err
|
|
340
|
-
} catch (err:
|
|
387
|
+
} catch (err: Exception) {
|
|
341
388
|
listenersAttached = false
|
|
342
389
|
val error = OpenIapError.InitConnection
|
|
343
390
|
val errorMessage = err.message ?: err.javaClass.name
|
|
@@ -374,7 +421,7 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
374
421
|
}
|
|
375
422
|
} catch (err: CancellationException) {
|
|
376
423
|
throw err
|
|
377
|
-
} catch (err:
|
|
424
|
+
} catch (err: Exception) {
|
|
378
425
|
val error = OpenIapError.InitConnection
|
|
379
426
|
RnIapLog.failure("initConnection.native", err)
|
|
380
427
|
throw OpenIapException(
|
|
@@ -470,7 +517,6 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
470
517
|
throw OpenIapException(toErrorJson(OpenIapError.EmptySkuList))
|
|
471
518
|
}
|
|
472
519
|
|
|
473
|
-
ensureConnection()
|
|
474
520
|
|
|
475
521
|
val queryType = parseProductQueryType(type)
|
|
476
522
|
val skusList = skus.toList()
|
|
@@ -545,9 +591,15 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
545
591
|
return@async defaultResult
|
|
546
592
|
}
|
|
547
593
|
|
|
594
|
+
if (rejectDisconnectedPurchase(isInitialized) { error ->
|
|
595
|
+
RnIapLog.warn("requestPurchase called before initConnection")
|
|
596
|
+
sendPurchaseError(toErrorResult(error))
|
|
597
|
+
}) {
|
|
598
|
+
return@async defaultResult
|
|
599
|
+
}
|
|
600
|
+
|
|
548
601
|
var reachedOpenIapRequest = false
|
|
549
602
|
try {
|
|
550
|
-
ensureConnection()
|
|
551
603
|
|
|
552
604
|
// Ensure Activity is available for purchase flow
|
|
553
605
|
val activity = withContext(Dispatchers.Main) {
|
|
@@ -565,29 +617,36 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
565
617
|
openIap.setActivity(activity)
|
|
566
618
|
}
|
|
567
619
|
|
|
568
|
-
val
|
|
569
|
-
|
|
620
|
+
val queryType = request.type?.let { requestType ->
|
|
621
|
+
when (requestType) {
|
|
622
|
+
NitroPurchaseRequestType.SUBS -> ProductQueryType.Subs
|
|
623
|
+
NitroPurchaseRequestType.IN_APP -> ProductQueryType.InApp
|
|
624
|
+
}
|
|
625
|
+
} ?: run {
|
|
626
|
+
val missingSkus = androidRequest.skus.filterNot { productTypeBySku.containsKey(it) }
|
|
570
627
|
missingSkus.forEach { sku ->
|
|
571
|
-
RnIapLog.warn("requestPurchase missing
|
|
572
|
-
val fetched =
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
628
|
+
RnIapLog.warn("requestPurchase missing type hint for $sku; attempting fetch")
|
|
629
|
+
val fetched = catchNonCancellation(
|
|
630
|
+
block = {
|
|
631
|
+
openIap.fetchProducts(
|
|
632
|
+
ProductRequest(listOf(sku), ProductQueryType.All)
|
|
633
|
+
).productsOrEmpty()
|
|
634
|
+
},
|
|
635
|
+
onFailure = { error ->
|
|
636
|
+
if (error is OpenIapError) throw error
|
|
637
|
+
RnIapLog.failure("requestPurchase.fetchMissing", error)
|
|
638
|
+
emptyList()
|
|
639
|
+
},
|
|
640
|
+
)
|
|
580
641
|
fetched.firstOrNull()?.let { productTypeBySku[it.id] = it.type.rawValue }
|
|
581
642
|
if (!productTypeBySku.containsKey(sku)) {
|
|
582
643
|
sendPurchaseError(toErrorResult(OpenIapError.SkuNotFound(sku)))
|
|
583
644
|
return@async defaultResult
|
|
584
645
|
}
|
|
585
646
|
}
|
|
647
|
+
parseProductQueryType(productTypeBySku[androidRequest.skus.first()] ?: "in-app")
|
|
586
648
|
}
|
|
587
649
|
|
|
588
|
-
val typeHint = androidRequest.skus.firstOrNull()?.let { productTypeBySku[it] } ?: "in-app"
|
|
589
|
-
val queryType = parseProductQueryType(typeHint)
|
|
590
|
-
|
|
591
650
|
try {
|
|
592
651
|
requireMatchingPurchaseOptions(
|
|
593
652
|
type = queryType,
|
|
@@ -697,6 +756,12 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
697
756
|
defaultResult
|
|
698
757
|
} catch (e: CancellationException) {
|
|
699
758
|
throw e
|
|
759
|
+
} catch (e: OpenIapError) {
|
|
760
|
+
RnIapLog.failure("requestPurchase", e)
|
|
761
|
+
if (!reachedOpenIapRequest) {
|
|
762
|
+
sendPurchaseError(toErrorResult(error = e, debugMessage = e.message))
|
|
763
|
+
}
|
|
764
|
+
defaultResult
|
|
700
765
|
} catch (e: Exception) {
|
|
701
766
|
RnIapLog.failure("requestPurchase", e)
|
|
702
767
|
if (!reachedOpenIapRequest) {
|
|
@@ -716,54 +781,53 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
716
781
|
// Purchase history methods (Unified)
|
|
717
782
|
override fun getAvailablePurchases(options: NitroAvailablePurchasesOptions?): Promise<Array<NitroPurchase>> {
|
|
718
783
|
return Promise.async {
|
|
719
|
-
|
|
720
|
-
|
|
784
|
+
try {
|
|
785
|
+
val androidOptions = (options?.android as? Variant_NullType_NitroAvailablePurchasesAndroidOptions.Second)?.value
|
|
721
786
|
|
|
722
|
-
|
|
787
|
+
val includeSuspended = androidOptions?.includeSuspended.unwrapBool() ?: false
|
|
723
788
|
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
789
|
+
RnIapLog.payload(
|
|
790
|
+
"getAvailablePurchases",
|
|
791
|
+
mapOf("type" to androidOptions?.type?.name, "includeSuspended" to includeSuspended)
|
|
792
|
+
)
|
|
728
793
|
|
|
729
|
-
|
|
730
|
-
val normalizedType = when (typeName) {
|
|
731
|
-
"in-app", "subs" -> typeName
|
|
732
|
-
else -> null
|
|
733
|
-
}
|
|
794
|
+
val normalizedType = normalizeAvailablePurchasesType(androidOptions?.type)
|
|
734
795
|
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
796
|
+
// Create PurchaseOptions with includeSuspendedAndroid
|
|
797
|
+
val purchaseOptions = dev.hyo.openiap.PurchaseOptions(
|
|
798
|
+
includeSuspendedAndroid = includeSuspended
|
|
799
|
+
)
|
|
739
800
|
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
801
|
+
val result: List<OpenIapPurchase> = if (normalizedType != null) {
|
|
802
|
+
val typeEnum = parseProductQueryType(normalizedType)
|
|
803
|
+
RnIapLog.payload(
|
|
804
|
+
"getAvailablePurchases.native",
|
|
805
|
+
mapOf("type" to typeEnum.rawValue, "includeSuspended" to includeSuspended)
|
|
806
|
+
)
|
|
807
|
+
// Note: getAvailableItems doesn't accept PurchaseOptions
|
|
808
|
+
// includeSuspended only applies when fetching all types
|
|
809
|
+
openIap.getAvailableItems(typeEnum)
|
|
810
|
+
} else {
|
|
811
|
+
RnIapLog.payload("getAvailablePurchases.native", mapOf("type" to "all", "includeSuspended" to includeSuspended))
|
|
812
|
+
openIap.getAvailablePurchases(purchaseOptions)
|
|
813
|
+
}
|
|
814
|
+
RnIapLog.result(
|
|
815
|
+
"getAvailablePurchases",
|
|
816
|
+
mapOf(
|
|
817
|
+
"purchaseCount" to result.size,
|
|
818
|
+
"productIds" to result.map { it.productId }
|
|
819
|
+
)
|
|
745
820
|
)
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
RnIapLog.payload("getAvailablePurchases.native", mapOf("type" to "all", "includeSuspended" to includeSuspended))
|
|
751
|
-
openIap.getAvailablePurchases(purchaseOptions)
|
|
821
|
+
result.map { convertToNitroPurchase(it) }.toTypedArray()
|
|
822
|
+
} catch (e: OpenIapError) {
|
|
823
|
+
RnIapLog.failure("getAvailablePurchases", e)
|
|
824
|
+
throw OpenIapException(toErrorJson(e), e)
|
|
752
825
|
}
|
|
753
|
-
RnIapLog.result(
|
|
754
|
-
"getAvailablePurchases",
|
|
755
|
-
mapOf(
|
|
756
|
-
"purchaseCount" to result.size,
|
|
757
|
-
"productIds" to result.map { it.productId }
|
|
758
|
-
)
|
|
759
|
-
)
|
|
760
|
-
result.map { convertToNitroPurchase(it) }.toTypedArray()
|
|
761
826
|
}
|
|
762
827
|
}
|
|
763
828
|
|
|
764
829
|
override fun getActiveSubscriptions(subscriptionIds: Array<String>?): Promise<Array<NitroActiveSubscription>> {
|
|
765
830
|
return Promise.async {
|
|
766
|
-
ensureConnection()
|
|
767
831
|
|
|
768
832
|
RnIapLog.payload(
|
|
769
833
|
"getActiveSubscriptions",
|
|
@@ -771,7 +835,6 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
771
835
|
)
|
|
772
836
|
|
|
773
837
|
try {
|
|
774
|
-
// Use OpenIapModule's native getActiveSubscriptions method
|
|
775
838
|
RnIapLog.payload("getActiveSubscriptions.native", mapOf("type" to "subs"))
|
|
776
839
|
val activeSubscriptions = openIap.getActiveSubscriptions(subscriptionIds?.toList())
|
|
777
840
|
|
|
@@ -801,6 +864,11 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
801
864
|
)
|
|
802
865
|
|
|
803
866
|
nitroSubscriptions.toTypedArray()
|
|
867
|
+
} catch (e: CancellationException) {
|
|
868
|
+
throw e
|
|
869
|
+
} catch (e: OpenIapError) {
|
|
870
|
+
RnIapLog.failure("getActiveSubscriptions", e)
|
|
871
|
+
throw OpenIapException(toErrorJson(e))
|
|
804
872
|
} catch (e: Exception) {
|
|
805
873
|
RnIapLog.failure("getActiveSubscriptions", e)
|
|
806
874
|
val error = OpenIapError.ServiceUnavailable()
|
|
@@ -817,7 +885,6 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
817
885
|
|
|
818
886
|
override fun hasActiveSubscriptions(subscriptionIds: Array<String>?): Promise<Boolean> {
|
|
819
887
|
return Promise.async {
|
|
820
|
-
ensureConnection()
|
|
821
888
|
|
|
822
889
|
RnIapLog.payload(
|
|
823
890
|
"hasActiveSubscriptions",
|
|
@@ -828,6 +895,11 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
828
895
|
val hasActive = openIap.hasActiveSubscriptions(subscriptionIds?.toList())
|
|
829
896
|
RnIapLog.result("hasActiveSubscriptions", hasActive)
|
|
830
897
|
hasActive
|
|
898
|
+
} catch (e: CancellationException) {
|
|
899
|
+
throw e
|
|
900
|
+
} catch (e: OpenIapError) {
|
|
901
|
+
RnIapLog.failure("hasActiveSubscriptions", e)
|
|
902
|
+
throw OpenIapException(toErrorJson(e))
|
|
831
903
|
} catch (e: Exception) {
|
|
832
904
|
RnIapLog.failure("hasActiveSubscriptions", e)
|
|
833
905
|
val error = OpenIapError.ServiceUnavailable()
|
|
@@ -877,27 +949,6 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
877
949
|
)
|
|
878
950
|
}
|
|
879
951
|
|
|
880
|
-
// Ensure connection; if it fails, return an error result instead of throwing
|
|
881
|
-
try {
|
|
882
|
-
ensureConnection()
|
|
883
|
-
} catch (e: Exception) {
|
|
884
|
-
val err = OpenIapError.InitConnection
|
|
885
|
-
return@async Variant_Boolean_NitroPurchaseResult.Second(
|
|
886
|
-
NitroPurchaseResult(
|
|
887
|
-
responseCode = -1.0,
|
|
888
|
-
debugMessage = e.message,
|
|
889
|
-
code = OpenIapError.toCode(err),
|
|
890
|
-
message = e.message?.takeIf { it.isNotBlank() } ?: err.message,
|
|
891
|
-
purchaseToken = purchaseToken,
|
|
892
|
-
productId = null,
|
|
893
|
-
productIds = null,
|
|
894
|
-
productType = null,
|
|
895
|
-
isEmptyProductList = null,
|
|
896
|
-
subResponseCodeAndroid = null
|
|
897
|
-
)
|
|
898
|
-
)
|
|
899
|
-
}
|
|
900
|
-
|
|
901
952
|
try {
|
|
902
953
|
if (isConsumable) {
|
|
903
954
|
openIap.consumePurchaseAndroid(purchaseToken)
|
|
@@ -920,6 +971,24 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
920
971
|
)
|
|
921
972
|
RnIapLog.result("finishTransaction", mapOf("success" to true))
|
|
922
973
|
result
|
|
974
|
+
} catch (e: CancellationException) {
|
|
975
|
+
throw e
|
|
976
|
+
} catch (e: OpenIapError) {
|
|
977
|
+
RnIapLog.failure("finishTransaction", e)
|
|
978
|
+
Variant_Boolean_NitroPurchaseResult.Second(
|
|
979
|
+
NitroPurchaseResult(
|
|
980
|
+
responseCode = -1.0,
|
|
981
|
+
debugMessage = e.message,
|
|
982
|
+
code = OpenIapError.toCode(e),
|
|
983
|
+
message = e.message,
|
|
984
|
+
purchaseToken = purchaseToken,
|
|
985
|
+
productId = null,
|
|
986
|
+
productIds = null,
|
|
987
|
+
productType = null,
|
|
988
|
+
isEmptyProductList = null,
|
|
989
|
+
subResponseCodeAndroid = null
|
|
990
|
+
)
|
|
991
|
+
)
|
|
923
992
|
} catch (e: Exception) {
|
|
924
993
|
val err = OpenIapError.BillingError()
|
|
925
994
|
RnIapLog.failure("finishTransaction", e)
|
|
@@ -944,7 +1013,6 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
944
1013
|
override fun getStorefront(): Promise<String> {
|
|
945
1014
|
return Promise.async {
|
|
946
1015
|
try {
|
|
947
|
-
ensureConnection()
|
|
948
1016
|
RnIapLog.payload("getStorefront", null)
|
|
949
1017
|
val value = openIap.getStorefront()
|
|
950
1018
|
if (value.isBlank()) {
|
|
@@ -954,6 +1022,8 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
954
1022
|
}
|
|
955
1023
|
RnIapLog.result("getStorefront", value)
|
|
956
1024
|
value
|
|
1025
|
+
} catch (e: CancellationException) {
|
|
1026
|
+
throw e
|
|
957
1027
|
} catch (e: OpenIapException) {
|
|
958
1028
|
RnIapLog.failure("getStorefront", e)
|
|
959
1029
|
throw e
|
|
@@ -1400,7 +1470,6 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
1400
1470
|
override fun deepLinkToSubscriptionsAndroid(options: NitroDeepLinkOptionsAndroid): Promise<Unit> {
|
|
1401
1471
|
return Promise.async {
|
|
1402
1472
|
try {
|
|
1403
|
-
ensureConnection()
|
|
1404
1473
|
OpenIapDeepLinkOptions(
|
|
1405
1474
|
skuAndroid = options.skuAndroid.unwrapString(),
|
|
1406
1475
|
packageNameAndroid = options.packageNameAndroid.unwrapString()
|
|
@@ -1568,6 +1637,8 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
1568
1637
|
|
|
1569
1638
|
Variant_NitroPurchaseVerificationResultIOS_NitroPurchaseVerificationResultAndroid_NitroPurchaseVerificationResultHorizon.Second(result)
|
|
1570
1639
|
|
|
1640
|
+
} catch (e: CancellationException) {
|
|
1641
|
+
throw e
|
|
1571
1642
|
} catch (e: OpenIapException) {
|
|
1572
1643
|
RnIapLog.failure("verifyPurchase", e)
|
|
1573
1644
|
throw e
|
|
@@ -1607,6 +1678,11 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
1607
1678
|
(iapkit.google as? Variant_NullType_NitroVerifyPurchaseWithIapkitGoogleProps.Second)?.value?.let { google ->
|
|
1608
1679
|
iapkitMap["google"] = mapOf("purchaseToken" to google.purchaseToken)
|
|
1609
1680
|
}
|
|
1681
|
+
(iapkit.horizon as? Variant_NullType_NitroVerifyPurchaseWithIapkitHorizonProps.Second)?.value?.let { horizon ->
|
|
1682
|
+
val horizonMap = mutableMapOf<String, Any?>("sku" to horizon.sku)
|
|
1683
|
+
horizon.userId.unwrapString()?.let { horizonMap["userId"] = it }
|
|
1684
|
+
iapkitMap["horizon"] = horizonMap
|
|
1685
|
+
}
|
|
1610
1686
|
(iapkit.amazon as? Variant_NullType_NitroVerifyPurchaseWithIapkitAmazonProps.Second)?.value?.let { amazon ->
|
|
1611
1687
|
val amazonMap = mutableMapOf<String, Any?>(
|
|
1612
1688
|
"receiptId" to amazon.receiptId
|
|
@@ -1673,6 +1749,8 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
1673
1749
|
errors = nitroErrors?.let { Variant_NullType_Array_NitroVerifyPurchaseWithProviderError_.Second(it) },
|
|
1674
1750
|
provider = mapPurchaseVerificationProvider(result.provider.rawValue)
|
|
1675
1751
|
)
|
|
1752
|
+
} catch (e: CancellationException) {
|
|
1753
|
+
throw e
|
|
1676
1754
|
} catch (e: Exception) {
|
|
1677
1755
|
RnIapLog.failure("verifyPurchaseWithProvider", e)
|
|
1678
1756
|
val error = OpenIapError.VerificationFailed
|
|
@@ -1764,9 +1842,7 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
1764
1842
|
}
|
|
1765
1843
|
|
|
1766
1844
|
override fun removeUserChoiceBillingListenerAndroid(listener: (UserChoiceBillingDetails) -> Unit) {
|
|
1767
|
-
|
|
1768
|
-
userChoiceBillingListenersAndroid.remove(listener)
|
|
1769
|
-
}
|
|
1845
|
+
removeSingletonBridgeListener(userChoiceBillingListenersAndroid, listener)
|
|
1770
1846
|
}
|
|
1771
1847
|
|
|
1772
1848
|
private fun sendUserChoiceBilling(details: UserChoiceBillingDetails) {
|
|
@@ -1782,9 +1858,7 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
1782
1858
|
}
|
|
1783
1859
|
|
|
1784
1860
|
override fun removeDeveloperProvidedBillingListenerAndroid(listener: (DeveloperProvidedBillingDetailsAndroid) -> Unit) {
|
|
1785
|
-
|
|
1786
|
-
developerProvidedBillingListenersAndroid.remove(listener)
|
|
1787
|
-
}
|
|
1861
|
+
removeSingletonBridgeListener(developerProvidedBillingListenersAndroid, listener)
|
|
1788
1862
|
}
|
|
1789
1863
|
|
|
1790
1864
|
private fun sendDeveloperProvidedBilling(details: DeveloperProvidedBillingDetailsAndroid) {
|
|
@@ -1813,9 +1887,7 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
1813
1887
|
|
|
1814
1888
|
override fun removeSubscriptionBillingIssueListener(listener: (purchase: NitroPurchase) -> Unit) {
|
|
1815
1889
|
synchronized(subscriptionBillingIssueAttachLock) {
|
|
1816
|
-
|
|
1817
|
-
subscriptionBillingIssueListeners.remove(listener)
|
|
1818
|
-
}
|
|
1890
|
+
removeSingletonBridgeListener(subscriptionBillingIssueListeners, listener)
|
|
1819
1891
|
}
|
|
1820
1892
|
}
|
|
1821
1893
|
|
|
@@ -1865,21 +1937,22 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
1865
1937
|
|
|
1866
1938
|
override fun enableBillingProgramAndroid(program: BillingProgramAndroid) {
|
|
1867
1939
|
RnIapLog.payload("enableBillingProgramAndroid", mapOf("program" to program.name))
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1940
|
+
catchNonCancellation(
|
|
1941
|
+
block = {
|
|
1942
|
+
val openIapProgram = mapBillingProgram(program)
|
|
1943
|
+
openIapStore.enableBillingProgram(openIapProgram)
|
|
1944
|
+
RnIapLog.result("enableBillingProgramAndroid", true)
|
|
1945
|
+
},
|
|
1946
|
+
onFailure = { err ->
|
|
1947
|
+
RnIapLog.failure("enableBillingProgramAndroid", err)
|
|
1948
|
+
},
|
|
1949
|
+
)
|
|
1876
1950
|
}
|
|
1877
1951
|
|
|
1878
1952
|
override fun isBillingProgramAvailableAndroid(program: BillingProgramAndroid): Promise<NitroBillingProgramAvailabilityResultAndroid> {
|
|
1879
1953
|
return Promise.async {
|
|
1880
1954
|
RnIapLog.payload("isBillingProgramAvailableAndroid", mapOf("program" to program.name))
|
|
1881
1955
|
try {
|
|
1882
|
-
ensureConnection()
|
|
1883
1956
|
val openIapProgram = mapBillingProgram(program)
|
|
1884
1957
|
val result = openIapStore.isBillingProgramAvailable(openIapProgram)
|
|
1885
1958
|
val nitroResult = NitroBillingProgramAvailabilityResultAndroid(
|
|
@@ -1890,7 +1963,9 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
1890
1963
|
)
|
|
1891
1964
|
RnIapLog.result("isBillingProgramAvailableAndroid", mapOf("isAvailable" to result.isAvailable))
|
|
1892
1965
|
nitroResult
|
|
1893
|
-
} catch (err:
|
|
1966
|
+
} catch (err: CancellationException) {
|
|
1967
|
+
throw err
|
|
1968
|
+
} catch (err: Exception) {
|
|
1894
1969
|
RnIapLog.failure("isBillingProgramAvailableAndroid", err)
|
|
1895
1970
|
val errorType = parseOpenIapError(err)
|
|
1896
1971
|
throw OpenIapException(toErrorJson(errorType, debugMessage = err.message), err)
|
|
@@ -1906,7 +1981,6 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
1906
1981
|
"userLocale" to params.userLocale.unwrapString()
|
|
1907
1982
|
))
|
|
1908
1983
|
try {
|
|
1909
|
-
ensureConnection()
|
|
1910
1984
|
val result = openIapStore.getBillingChoiceInfo(
|
|
1911
1985
|
OpenIapGetBillingChoiceInfoParams(
|
|
1912
1986
|
billingProgram = mapBillingProgram(params.billingProgram),
|
|
@@ -1920,7 +1994,9 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
1920
1994
|
)
|
|
1921
1995
|
RnIapLog.result("getBillingChoiceInfoAndroid", mapOf("hasImageUrl" to result.playBillingChoiceImageUrl.isNotBlank()))
|
|
1922
1996
|
nitroResult
|
|
1923
|
-
} catch (err:
|
|
1997
|
+
} catch (err: CancellationException) {
|
|
1998
|
+
throw err
|
|
1999
|
+
} catch (err: Exception) {
|
|
1924
2000
|
RnIapLog.failure("getBillingChoiceInfoAndroid", err)
|
|
1925
2001
|
val errorType = parseOpenIapError(err)
|
|
1926
2002
|
throw OpenIapException(toErrorJson(errorType, debugMessage = err.message), err)
|
|
@@ -1938,7 +2014,6 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
1938
2014
|
mapOf("program" to program.name, "developerBillingType" to developerBillingType?.name)
|
|
1939
2015
|
)
|
|
1940
2016
|
try {
|
|
1941
|
-
ensureConnection()
|
|
1942
2017
|
val openIapProgram = mapBillingProgram(program)
|
|
1943
2018
|
val result = openIapStore.createBillingProgramReportingDetails(
|
|
1944
2019
|
openIapProgram,
|
|
@@ -1950,7 +2025,9 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
1950
2025
|
)
|
|
1951
2026
|
RnIapLog.result("createBillingProgramReportingDetailsAndroid", mapOf("hasToken" to true))
|
|
1952
2027
|
nitroResult
|
|
1953
|
-
} catch (err:
|
|
2028
|
+
} catch (err: CancellationException) {
|
|
2029
|
+
throw err
|
|
2030
|
+
} catch (err: Exception) {
|
|
1954
2031
|
RnIapLog.failure("createBillingProgramReportingDetailsAndroid", err)
|
|
1955
2032
|
val errorType = parseOpenIapError(err)
|
|
1956
2033
|
throw OpenIapException(toErrorJson(errorType, debugMessage = err.message), err)
|
|
@@ -1964,7 +2041,6 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
1964
2041
|
return Promise.async {
|
|
1965
2042
|
RnIapLog.payload("showBillingProgramInformationDialogAndroid", mapOf("program" to params.billingProgram.name))
|
|
1966
2043
|
try {
|
|
1967
|
-
ensureConnection()
|
|
1968
2044
|
val activity = withContext(Dispatchers.Main) {
|
|
1969
2045
|
runCatching { context.currentActivity }.getOrNull()
|
|
1970
2046
|
} ?: throw OpenIapException(toErrorJson(OpenIapError.DeveloperError(), debugMessage = "Activity not available"))
|
|
@@ -1983,7 +2059,9 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
1983
2059
|
debugMessage = result.debugMessage.wrapVariant(),
|
|
1984
2060
|
subResponseCode = mapSubResponseCode(result.subResponseCode)
|
|
1985
2061
|
)
|
|
1986
|
-
} catch (err:
|
|
2062
|
+
} catch (err: CancellationException) {
|
|
2063
|
+
throw err
|
|
2064
|
+
} catch (err: Exception) {
|
|
1987
2065
|
RnIapLog.failure("showBillingProgramInformationDialogAndroid", err)
|
|
1988
2066
|
val errorType = parseOpenIapError(err)
|
|
1989
2067
|
throw OpenIapException(toErrorJson(errorType, debugMessage = err.message), err)
|
|
@@ -1999,7 +2077,6 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
1999
2077
|
val categories = messageParams?.categories?.asSecondOrNull()
|
|
2000
2078
|
RnIapLog.payload("showInAppMessagesAndroid", mapOf("categories" to categories?.map { it.name }))
|
|
2001
2079
|
try {
|
|
2002
|
-
ensureConnection()
|
|
2003
2080
|
val activity = withContext(Dispatchers.Main) {
|
|
2004
2081
|
runCatching { context.currentActivity }.getOrNull()
|
|
2005
2082
|
} ?: throw OpenIapException(toErrorJson(OpenIapError.DeveloperError(), debugMessage = "Activity not available"))
|
|
@@ -2018,7 +2095,9 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
2018
2095
|
responseCode = mapInAppMessageResponseCode(result.responseCode),
|
|
2019
2096
|
purchaseToken = result.purchaseToken.wrapVariant()
|
|
2020
2097
|
)
|
|
2021
|
-
} catch (err:
|
|
2098
|
+
} catch (err: CancellationException) {
|
|
2099
|
+
throw err
|
|
2100
|
+
} catch (err: Exception) {
|
|
2022
2101
|
RnIapLog.failure("showInAppMessagesAndroid", err)
|
|
2023
2102
|
val errorType = parseOpenIapError(err)
|
|
2024
2103
|
throw OpenIapException(toErrorJson(errorType, debugMessage = err.message), err)
|
|
@@ -2035,7 +2114,6 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
2035
2114
|
"linkUri" to params.linkUri
|
|
2036
2115
|
))
|
|
2037
2116
|
try {
|
|
2038
|
-
ensureConnection()
|
|
2039
2117
|
|
|
2040
2118
|
val activity = withContext(Dispatchers.Main) {
|
|
2041
2119
|
runCatching { context.currentActivity }.getOrNull()
|
|
@@ -2054,7 +2132,9 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
2054
2132
|
}
|
|
2055
2133
|
RnIapLog.result("launchExternalLinkAndroid", result)
|
|
2056
2134
|
result
|
|
2057
|
-
} catch (err:
|
|
2135
|
+
} catch (err: CancellationException) {
|
|
2136
|
+
throw err
|
|
2137
|
+
} catch (err: Exception) {
|
|
2058
2138
|
RnIapLog.failure("launchExternalLinkAndroid", err)
|
|
2059
2139
|
val errorType = parseOpenIapError(err)
|
|
2060
2140
|
throw OpenIapException(toErrorJson(errorType, debugMessage = err.message))
|
|
@@ -2074,7 +2154,9 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
2074
2154
|
val result = handler()
|
|
2075
2155
|
RnIapLog.result("openRedeemOfferCodeAndroid", result)
|
|
2076
2156
|
result
|
|
2077
|
-
} catch (err:
|
|
2157
|
+
} catch (err: CancellationException) {
|
|
2158
|
+
throw err
|
|
2159
|
+
} catch (err: Exception) {
|
|
2078
2160
|
RnIapLog.failure("openRedeemOfferCodeAndroid", err)
|
|
2079
2161
|
val errorType = parseOpenIapError(err)
|
|
2080
2162
|
throw OpenIapException(toErrorJson(errorType, debugMessage = err.message), err)
|
|
@@ -2230,30 +2312,6 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
2230
2312
|
}
|
|
2231
2313
|
}
|
|
2232
2314
|
|
|
2233
|
-
// ---------------------------------------------------------------------
|
|
2234
|
-
// OpenIAP error helpers: unify error codes/messages from library
|
|
2235
|
-
// ---------------------------------------------------------------------
|
|
2236
|
-
private fun parseOpenIapError(err: Throwable): OpenIapError {
|
|
2237
|
-
// Try to extract OpenIapError from the exception chain
|
|
2238
|
-
var cause: Throwable? = err
|
|
2239
|
-
while (cause != null) {
|
|
2240
|
-
val message = cause.message ?: ""
|
|
2241
|
-
// Check if message contains OpenIAP error patterns
|
|
2242
|
-
when {
|
|
2243
|
-
message.contains("not prepared", ignoreCase = true) ||
|
|
2244
|
-
message.contains("not initialized", ignoreCase = true) -> return OpenIapError.NotPrepared
|
|
2245
|
-
message.contains("developer error", ignoreCase = true) ||
|
|
2246
|
-
message.contains("activity not available", ignoreCase = true) -> return OpenIapError.DeveloperError()
|
|
2247
|
-
message.contains("network", ignoreCase = true) -> return OpenIapError.NetworkError
|
|
2248
|
-
message.contains("service unavailable", ignoreCase = true) ||
|
|
2249
|
-
message.contains("billing unavailable", ignoreCase = true) -> return OpenIapError.ServiceUnavailable()
|
|
2250
|
-
}
|
|
2251
|
-
cause = cause.cause
|
|
2252
|
-
}
|
|
2253
|
-
// Default to ServiceUnavailable if we can't determine the error type
|
|
2254
|
-
return OpenIapError.ServiceUnavailable()
|
|
2255
|
-
}
|
|
2256
|
-
|
|
2257
2315
|
private fun toErrorJson(
|
|
2258
2316
|
error: OpenIapError,
|
|
2259
2317
|
productId: String? = null,
|