react-native-iap 15.2.0 → 15.2.2
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 +117 -114
- package/android/src/main/java/com/margelo/nitro/iap/ProductQueryHelpers.kt +42 -0
- package/android/src/test/java/com/margelo/nitro/iap/ProductQueryHelpersTest.kt +140 -0
- package/ios/HybridRnIap.swift +33 -0
- package/lib/module/hooks/useIAP.js.map +1 -1
- package/lib/module/hooks/useWebhookEvents.js +113 -0
- package/lib/module/hooks/useWebhookEvents.js.map +1 -0
- package/lib/module/index.js +331 -131
- package/lib/module/index.js.map +1 -1
- package/lib/module/kit-api.js +161 -0
- package/lib/module/kit-api.js.map +1 -0
- package/lib/module/types.js +16 -0
- package/lib/module/types.js.map +1 -1
- package/lib/module/utils/error.js.map +1 -1
- package/lib/module/utils/errorMapping.js +6 -0
- package/lib/module/utils/errorMapping.js.map +1 -1
- package/lib/module/webhook-client.js +164 -0
- package/lib/module/webhook-client.js.map +1 -0
- package/lib/typescript/plugin/src/withIAP.d.ts +1 -1
- package/lib/typescript/src/hooks/useIAP.d.ts +162 -2
- package/lib/typescript/src/hooks/useIAP.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useWebhookEvents.d.ts +55 -0
- package/lib/typescript/src/hooks/useWebhookEvents.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +282 -129
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/kit-api.d.ts +54 -0
- package/lib/typescript/src/kit-api.d.ts.map +1 -0
- package/lib/typescript/src/specs/RnIap.nitro.d.ts +7 -0
- package/lib/typescript/src/specs/RnIap.nitro.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +304 -74
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/utils/error.d.ts +3 -0
- package/lib/typescript/src/utils/error.d.ts.map +1 -1
- package/lib/typescript/src/utils/errorMapping.d.ts +6 -0
- package/lib/typescript/src/utils/errorMapping.d.ts.map +1 -1
- package/lib/typescript/src/webhook-client.d.ts +82 -0
- package/lib/typescript/src/webhook-client.d.ts.map +1 -0
- package/nitrogen/generated/android/NitroIap+autolinking.cmake +3 -0
- package/nitrogen/generated/android/c++/JAdvancedCommerceInfoIOS.hpp +118 -0
- package/nitrogen/generated/android/c++/JAdvancedCommerceItemDetailsIOS.hpp +62 -0
- package/nitrogen/generated/android/c++/JAdvancedCommerceItemIOS.hpp +78 -0
- package/nitrogen/generated/android/c++/JAdvancedCommerceRefundIOS.hpp +62 -0
- package/nitrogen/generated/android/c++/JHybridRnIapSpec.cpp +44 -0
- package/nitrogen/generated/android/c++/JHybridRnIapSpec.hpp +1 -0
- package/nitrogen/generated/android/c++/JPurchase.hpp +11 -0
- package/nitrogen/generated/android/c++/JPurchaseIOS.hpp +16 -1
- package/nitrogen/generated/android/c++/JRequestPurchaseResult.hpp +11 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_AdvancedCommerceInfoIOS.cpp +26 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_AdvancedCommerceInfoIOS.hpp +84 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_AdvancedCommerceItemDetailsIOS.cpp +26 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_AdvancedCommerceItemDetailsIOS.hpp +74 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_Array_AdvancedCommerceRefundIOS_.cpp +35 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_Array_AdvancedCommerceRefundIOS_.hpp +84 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/AdvancedCommerceInfoIOS.kt +59 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/AdvancedCommerceItemDetailsIOS.kt +38 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/AdvancedCommerceItemIOS.kt +44 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/AdvancedCommerceRefundIOS.kt +38 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/HybridRnIapSpec.kt +4 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/PurchaseIOS.kt +5 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/Variant_NullType_AdvancedCommerceInfoIOS.kt +53 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/Variant_NullType_AdvancedCommerceItemDetailsIOS.kt +53 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/Variant_NullType_Array_AdvancedCommerceRefundIOS_.kt +53 -0
- package/nitrogen/generated/ios/NitroIap-Swift-Cxx-Bridge.hpp +166 -0
- package/nitrogen/generated/ios/NitroIap-Swift-Cxx-Umbrella.hpp +12 -0
- package/nitrogen/generated/ios/c++/HybridRnIapSpecSwift.hpp +20 -0
- package/nitrogen/generated/ios/swift/AdvancedCommerceInfoIOS.swift +294 -0
- package/nitrogen/generated/ios/swift/AdvancedCommerceItemDetailsIOS.swift +61 -0
- package/nitrogen/generated/ios/swift/AdvancedCommerceItemIOS.swift +141 -0
- package/nitrogen/generated/ios/swift/AdvancedCommerceRefundIOS.swift +61 -0
- package/nitrogen/generated/ios/swift/HybridRnIapSpec.swift +1 -0
- package/nitrogen/generated/ios/swift/HybridRnIapSpec_cxx.swift +25 -0
- package/nitrogen/generated/ios/swift/PurchaseIOS.swift +39 -2
- package/nitrogen/generated/ios/swift/Variant_NullType_AdvancedCommerceInfoIOS.swift +18 -0
- package/nitrogen/generated/ios/swift/Variant_NullType_AdvancedCommerceItemDetailsIOS.swift +18 -0
- package/nitrogen/generated/ios/swift/Variant_NullType__AdvancedCommerceRefundIOS_.swift +18 -0
- package/nitrogen/generated/shared/c++/AdvancedCommerceInfoIOS.hpp +117 -0
- package/nitrogen/generated/shared/c++/AdvancedCommerceItemDetailsIOS.hpp +86 -0
- package/nitrogen/generated/shared/c++/AdvancedCommerceItemIOS.hpp +99 -0
- package/nitrogen/generated/shared/c++/AdvancedCommerceRefundIOS.hpp +86 -0
- package/nitrogen/generated/shared/c++/HybridRnIapSpec.cpp +1 -0
- package/nitrogen/generated/shared/c++/HybridRnIapSpec.hpp +1 -0
- package/nitrogen/generated/shared/c++/PurchaseIOS.hpp +9 -2
- package/openiap-versions.json +3 -3
- package/package.json +1 -1
- package/plugin/build/withIAP.d.ts +1 -1
- package/plugin/src/withIAP.ts +1 -1
- package/src/hooks/useIAP.ts +162 -2
- package/src/hooks/useWebhookEvents.ts +180 -0
- package/src/index.ts +348 -130
- package/src/kit-api.ts +225 -0
- package/src/specs/RnIap.nitro.ts +8 -0
- package/src/types.ts +314 -74
- package/src/utils/error.ts +3 -0
- package/src/utils/errorMapping.ts +12 -0
- package/src/webhook-client.ts +312 -0
|
@@ -10,7 +10,7 @@ import dev.hyo.openiap.FetchProductsResult
|
|
|
10
10
|
import dev.hyo.openiap.FetchProductsResultAll
|
|
11
11
|
import dev.hyo.openiap.FetchProductsResultProducts
|
|
12
12
|
import dev.hyo.openiap.FetchProductsResultSubscriptions
|
|
13
|
-
import dev.hyo.openiap.OpenIapError
|
|
13
|
+
import dev.hyo.openiap.OpenIapError
|
|
14
14
|
import dev.hyo.openiap.OpenIapModule
|
|
15
15
|
import dev.hyo.openiap.ProductAndroid
|
|
16
16
|
import dev.hyo.openiap.ProductQueryType
|
|
@@ -134,7 +134,7 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
134
134
|
} catch (err: CancellationException) {
|
|
135
135
|
throw err
|
|
136
136
|
} catch (err: Throwable) {
|
|
137
|
-
val error =
|
|
137
|
+
val error = OpenIapError.InitConnection
|
|
138
138
|
val errorMessage = err.message ?: err.javaClass.name
|
|
139
139
|
RnIapLog.failure("initConnection.setActivity", err)
|
|
140
140
|
throw OpenIapException(
|
|
@@ -173,22 +173,14 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
173
173
|
}.onFailure { RnIapLog.failure("purchaseUpdatedListener", it) }
|
|
174
174
|
})
|
|
175
175
|
openIap.addPurchaseErrorListener(OpenIapPurchaseErrorListener { e ->
|
|
176
|
-
val code =
|
|
177
|
-
val message = e.message ?:
|
|
176
|
+
val code = OpenIapError.toCode(e)
|
|
177
|
+
val message = e.message ?: OpenIapError.defaultMessage(code)
|
|
178
178
|
runCatching {
|
|
179
179
|
RnIapLog.result(
|
|
180
180
|
"purchaseErrorListener",
|
|
181
181
|
mapOf("code" to code, "message" to message)
|
|
182
182
|
)
|
|
183
|
-
sendPurchaseError(
|
|
184
|
-
NitroPurchaseResult(
|
|
185
|
-
responseCode = -1.0,
|
|
186
|
-
debugMessage = null,
|
|
187
|
-
code = code,
|
|
188
|
-
message = message,
|
|
189
|
-
purchaseToken = null
|
|
190
|
-
)
|
|
191
|
-
)
|
|
183
|
+
sendPurchaseError(toErrorResult(e))
|
|
192
184
|
}.onFailure { RnIapLog.failure("purchaseErrorListener", it) }
|
|
193
185
|
})
|
|
194
186
|
openIap.addUserChoiceBillingListener(OpenIapUserChoiceBillingListener { details ->
|
|
@@ -223,7 +215,7 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
223
215
|
throw err
|
|
224
216
|
} catch (err: Throwable) {
|
|
225
217
|
listenersAttached = false
|
|
226
|
-
val error =
|
|
218
|
+
val error = OpenIapError.InitConnection
|
|
227
219
|
val errorMessage = err.message ?: err.javaClass.name
|
|
228
220
|
RnIapLog.failure("initConnection.listeners", err)
|
|
229
221
|
val wrapped = OpenIapException(
|
|
@@ -267,7 +259,7 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
267
259
|
openIap.initConnection(openIapConfig)
|
|
268
260
|
}
|
|
269
261
|
} catch (err: Throwable) {
|
|
270
|
-
val error =
|
|
262
|
+
val error = OpenIapError.InitConnection
|
|
271
263
|
RnIapLog.failure("initConnection.native", err)
|
|
272
264
|
throw OpenIapException(
|
|
273
265
|
toErrorJson(
|
|
@@ -278,7 +270,7 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
278
270
|
)
|
|
279
271
|
}
|
|
280
272
|
if (!ok) {
|
|
281
|
-
val error =
|
|
273
|
+
val error = OpenIapError.InitConnection
|
|
282
274
|
RnIapLog.failure("initConnection.native", Exception(error.message))
|
|
283
275
|
throw OpenIapException(
|
|
284
276
|
toErrorJson(
|
|
@@ -335,7 +327,7 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
335
327
|
)
|
|
336
328
|
|
|
337
329
|
if (skus.isEmpty()) {
|
|
338
|
-
throw OpenIapException(toErrorJson(
|
|
330
|
+
throw OpenIapException(toErrorJson(OpenIapError.EmptySkuList))
|
|
339
331
|
}
|
|
340
332
|
|
|
341
333
|
ensureConnection()
|
|
@@ -343,46 +335,46 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
343
335
|
val queryType = parseProductQueryType(type)
|
|
344
336
|
val skusList = skus.toList()
|
|
345
337
|
|
|
346
|
-
val products: List<ProductCommon> =
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
338
|
+
val products: List<ProductCommon> = try {
|
|
339
|
+
when (queryType) {
|
|
340
|
+
ProductQueryType.All -> {
|
|
341
|
+
collectAllQueryProducts(
|
|
342
|
+
skusList = skusList,
|
|
343
|
+
fetchKind = { kind ->
|
|
344
|
+
RnIapLog.payload(
|
|
345
|
+
"fetchProducts.native",
|
|
346
|
+
mapOf("skus" to skusList, "type" to kind.rawValue)
|
|
347
|
+
)
|
|
348
|
+
val fetched = openIap.fetchProducts(ProductRequest(skusList, kind)).productsOrEmpty()
|
|
349
|
+
RnIapLog.result(
|
|
350
|
+
"fetchProducts.native",
|
|
351
|
+
fetched.map { mapOf("id" to it.id, "type" to it.type.rawValue) }
|
|
352
|
+
)
|
|
353
|
+
fetched
|
|
354
|
+
},
|
|
355
|
+
onFailure = { kind, error ->
|
|
356
|
+
RnIapLog.failure("fetchProducts.native[${kind.rawValue}]", error)
|
|
357
|
+
},
|
|
358
|
+
)
|
|
359
|
+
}
|
|
360
|
+
else -> {
|
|
352
361
|
RnIapLog.payload(
|
|
353
362
|
"fetchProducts.native",
|
|
354
|
-
mapOf("skus" to skusList, "type" to
|
|
363
|
+
mapOf("skus" to skusList, "type" to queryType.rawValue)
|
|
355
364
|
)
|
|
356
|
-
val fetched = openIap.fetchProducts(ProductRequest(skusList,
|
|
365
|
+
val fetched = openIap.fetchProducts(ProductRequest(skusList, queryType)).productsOrEmpty()
|
|
357
366
|
RnIapLog.result(
|
|
358
367
|
"fetchProducts.native",
|
|
359
368
|
fetched.map { mapOf("id" to it.id, "type" to it.type.rawValue) }
|
|
360
369
|
)
|
|
361
370
|
|
|
362
|
-
//
|
|
363
|
-
fetched.
|
|
364
|
-
|
|
365
|
-
}
|
|
371
|
+
// Preserve input order for non-All queries
|
|
372
|
+
val byId = fetched.associateBy { it.id }
|
|
373
|
+
skusList.mapNotNull { byId[it] }
|
|
366
374
|
}
|
|
367
|
-
|
|
368
|
-
// Return products in the same order as input skusList
|
|
369
|
-
skusList.mapNotNull { byId[it] }
|
|
370
|
-
}
|
|
371
|
-
else -> {
|
|
372
|
-
RnIapLog.payload(
|
|
373
|
-
"fetchProducts.native",
|
|
374
|
-
mapOf("skus" to skusList, "type" to queryType.rawValue)
|
|
375
|
-
)
|
|
376
|
-
val fetched = openIap.fetchProducts(ProductRequest(skusList, queryType)).productsOrEmpty()
|
|
377
|
-
RnIapLog.result(
|
|
378
|
-
"fetchProducts.native",
|
|
379
|
-
fetched.map { mapOf("id" to it.id, "type" to it.type.rawValue) }
|
|
380
|
-
)
|
|
381
|
-
|
|
382
|
-
// Preserve input order for non-All queries
|
|
383
|
-
val byId = fetched.associateBy { it.id }
|
|
384
|
-
skusList.mapNotNull { byId[it] }
|
|
385
375
|
}
|
|
376
|
+
} catch (e: OpenIapError) {
|
|
377
|
+
throw OpenIapException(toErrorJson(e))
|
|
386
378
|
}
|
|
387
379
|
|
|
388
380
|
products.forEach { p -> productTypeBySku[p.id] = p.type.rawValue }
|
|
@@ -414,13 +406,13 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
414
406
|
|
|
415
407
|
if (androidRequest == null) {
|
|
416
408
|
RnIapLog.warn("requestPurchase called without android payload")
|
|
417
|
-
sendPurchaseError(toErrorResult(
|
|
409
|
+
sendPurchaseError(toErrorResult(OpenIapError.DeveloperError()))
|
|
418
410
|
return@async defaultResult
|
|
419
411
|
}
|
|
420
412
|
|
|
421
413
|
if (androidRequest.skus.isEmpty()) {
|
|
422
414
|
RnIapLog.warn("requestPurchase received empty SKU list")
|
|
423
|
-
sendPurchaseError(toErrorResult(
|
|
415
|
+
sendPurchaseError(toErrorResult(OpenIapError.EmptySkuList))
|
|
424
416
|
return@async defaultResult
|
|
425
417
|
}
|
|
426
418
|
|
|
@@ -435,7 +427,7 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
435
427
|
|
|
436
428
|
if (activity == null) {
|
|
437
429
|
RnIapLog.warn("requestPurchase: Activity is null - cannot start purchase flow")
|
|
438
|
-
sendPurchaseError(toErrorResult(
|
|
430
|
+
sendPurchaseError(toErrorResult(OpenIapError.MissingCurrentActivity))
|
|
439
431
|
return@async defaultResult
|
|
440
432
|
}
|
|
441
433
|
|
|
@@ -457,7 +449,7 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
457
449
|
}
|
|
458
450
|
fetched.firstOrNull()?.let { productTypeBySku[it.id] = it.type.rawValue }
|
|
459
451
|
if (!productTypeBySku.containsKey(sku)) {
|
|
460
|
-
sendPurchaseError(toErrorResult(
|
|
452
|
+
sendPurchaseError(toErrorResult(OpenIapError.SkuNotFound(sku)))
|
|
461
453
|
return@async defaultResult
|
|
462
454
|
}
|
|
463
455
|
}
|
|
@@ -552,7 +544,7 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
552
544
|
RnIapLog.failure("requestPurchase", e)
|
|
553
545
|
sendPurchaseError(
|
|
554
546
|
toErrorResult(
|
|
555
|
-
error =
|
|
547
|
+
error = OpenIapError.PurchaseFailed(),
|
|
556
548
|
debugMessage = e.message,
|
|
557
549
|
messageOverride = e.message
|
|
558
550
|
)
|
|
@@ -654,7 +646,7 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
654
646
|
nitroSubscriptions.toTypedArray()
|
|
655
647
|
} catch (e: Exception) {
|
|
656
648
|
RnIapLog.failure("getActiveSubscriptions", e)
|
|
657
|
-
val error =
|
|
649
|
+
val error = OpenIapError.ServiceUnavailable()
|
|
658
650
|
throw OpenIapException(
|
|
659
651
|
toErrorJson(
|
|
660
652
|
error = error,
|
|
@@ -681,7 +673,7 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
681
673
|
hasActive
|
|
682
674
|
} catch (e: Exception) {
|
|
683
675
|
RnIapLog.failure("hasActiveSubscriptions", e)
|
|
684
|
-
val error =
|
|
676
|
+
val error = OpenIapError.ServiceUnavailable()
|
|
685
677
|
throw OpenIapException(
|
|
686
678
|
toErrorJson(
|
|
687
679
|
error = error,
|
|
@@ -716,7 +708,7 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
716
708
|
NitroPurchaseResult(
|
|
717
709
|
responseCode = -1.0,
|
|
718
710
|
debugMessage = "Missing purchaseToken",
|
|
719
|
-
code =
|
|
711
|
+
code = OpenIapError.toCode(OpenIapError.DeveloperError()),
|
|
720
712
|
message = "Missing purchaseToken",
|
|
721
713
|
purchaseToken = null
|
|
722
714
|
)
|
|
@@ -727,12 +719,12 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
727
719
|
try {
|
|
728
720
|
ensureConnection()
|
|
729
721
|
} catch (e: Exception) {
|
|
730
|
-
val err =
|
|
722
|
+
val err = OpenIapError.InitConnection
|
|
731
723
|
return@async Variant_Boolean_NitroPurchaseResult.Second(
|
|
732
724
|
NitroPurchaseResult(
|
|
733
725
|
responseCode = -1.0,
|
|
734
726
|
debugMessage = e.message,
|
|
735
|
-
code =
|
|
727
|
+
code = OpenIapError.toCode(err),
|
|
736
728
|
message = e.message?.takeIf { it.isNotBlank() } ?: err.message,
|
|
737
729
|
purchaseToken = purchaseToken
|
|
738
730
|
)
|
|
@@ -757,13 +749,13 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
757
749
|
RnIapLog.result("finishTransaction", mapOf("success" to true))
|
|
758
750
|
result
|
|
759
751
|
} catch (e: Exception) {
|
|
760
|
-
val err =
|
|
752
|
+
val err = OpenIapError.BillingError()
|
|
761
753
|
RnIapLog.failure("finishTransaction", e)
|
|
762
754
|
Variant_Boolean_NitroPurchaseResult.Second(
|
|
763
755
|
NitroPurchaseResult(
|
|
764
756
|
responseCode = -1.0,
|
|
765
757
|
debugMessage = e.message,
|
|
766
|
-
code =
|
|
758
|
+
code = OpenIapError.toCode(err),
|
|
767
759
|
message = e.message?.takeIf { it.isNotBlank() } ?: err.message,
|
|
768
760
|
purchaseToken = null
|
|
769
761
|
)
|
|
@@ -1279,14 +1271,14 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
1279
1271
|
// iOS-specific method - not supported on Android
|
|
1280
1272
|
override fun getStorefrontIOS(): Promise<String> {
|
|
1281
1273
|
return Promise.async {
|
|
1282
|
-
throw OpenIapException(toErrorJson(
|
|
1274
|
+
throw OpenIapException(toErrorJson(OpenIapError.FeatureNotSupported()))
|
|
1283
1275
|
}
|
|
1284
1276
|
}
|
|
1285
1277
|
|
|
1286
1278
|
// iOS-specific method - not supported on Android
|
|
1287
1279
|
override fun getAppTransactionIOS(): Promise<Variant_NullType_String> {
|
|
1288
1280
|
return Promise.async {
|
|
1289
|
-
throw OpenIapException(toErrorJson(
|
|
1281
|
+
throw OpenIapException(toErrorJson(OpenIapError.FeatureNotSupported()))
|
|
1290
1282
|
}
|
|
1291
1283
|
}
|
|
1292
1284
|
|
|
@@ -1373,7 +1365,7 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
1373
1365
|
try {
|
|
1374
1366
|
// For Android, we need the google options to be provided (new platform-specific structure)
|
|
1375
1367
|
val nitroGoogleOptions = (params.google as? Variant_NullType_NitroReceiptValidationGoogleOptions.Second)?.value
|
|
1376
|
-
?: throw OpenIapException(toErrorJson(
|
|
1368
|
+
?: throw OpenIapException(toErrorJson(OpenIapError.DeveloperError(), debugMessage = "Missing required parameter: google options"))
|
|
1377
1369
|
|
|
1378
1370
|
// Validate required google fields
|
|
1379
1371
|
val validations = mapOf(
|
|
@@ -1384,7 +1376,7 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
1384
1376
|
)
|
|
1385
1377
|
for ((name, value) in validations) {
|
|
1386
1378
|
if (value.isEmpty()) {
|
|
1387
|
-
throw OpenIapException(toErrorJson(
|
|
1379
|
+
throw OpenIapException(toErrorJson(OpenIapError.DeveloperError(), debugMessage = "Missing or empty required parameter: $name"))
|
|
1388
1380
|
}
|
|
1389
1381
|
}
|
|
1390
1382
|
|
|
@@ -1412,7 +1404,7 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
1412
1404
|
|
|
1413
1405
|
// Cast to Android result type (on Android, verifyPurchase returns VerifyPurchaseResultAndroid)
|
|
1414
1406
|
val androidResult = verifyResult as? VerifyPurchaseResultAndroid
|
|
1415
|
-
?: throw OpenIapException(toErrorJson(
|
|
1407
|
+
?: throw OpenIapException(toErrorJson(OpenIapError.InvalidPurchaseVerification, debugMessage = "Unexpected result type from verifyPurchase"))
|
|
1416
1408
|
|
|
1417
1409
|
// Convert OpenIAP result to Nitro result
|
|
1418
1410
|
val result = NitroReceiptValidationResultAndroid(
|
|
@@ -1444,7 +1436,7 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
1444
1436
|
} catch (e: Exception) {
|
|
1445
1437
|
RnIapLog.failure("validateReceipt", e)
|
|
1446
1438
|
val debugMessage = e.message
|
|
1447
|
-
val error =
|
|
1439
|
+
val error = OpenIapError.InvalidPurchaseVerification
|
|
1448
1440
|
throw OpenIapException(
|
|
1449
1441
|
toErrorJson(
|
|
1450
1442
|
error = error,
|
|
@@ -1509,7 +1501,7 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
1509
1501
|
)
|
|
1510
1502
|
} catch (e: Exception) {
|
|
1511
1503
|
RnIapLog.failure("verifyPurchaseWithProvider", e)
|
|
1512
|
-
val error =
|
|
1504
|
+
val error = OpenIapError.VerificationFailed
|
|
1513
1505
|
throw OpenIapException(
|
|
1514
1506
|
toErrorJson(
|
|
1515
1507
|
error = error,
|
|
@@ -1524,31 +1516,37 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
1524
1516
|
// iOS-specific methods - Not applicable on Android, return appropriate defaults
|
|
1525
1517
|
override fun subscriptionStatusIOS(sku: String): Promise<Variant_NullType_Array_NitroSubscriptionStatus_> {
|
|
1526
1518
|
return Promise.async {
|
|
1527
|
-
throw OpenIapException(toErrorJson(
|
|
1519
|
+
throw OpenIapException(toErrorJson(OpenIapError.FeatureNotSupported()))
|
|
1528
1520
|
}
|
|
1529
1521
|
}
|
|
1530
1522
|
|
|
1531
1523
|
override fun currentEntitlementIOS(sku: String): Promise<Variant_NullType_NitroPurchase> {
|
|
1532
1524
|
return Promise.async {
|
|
1533
|
-
throw OpenIapException(toErrorJson(
|
|
1525
|
+
throw OpenIapException(toErrorJson(OpenIapError.FeatureNotSupported()))
|
|
1534
1526
|
}
|
|
1535
1527
|
}
|
|
1536
1528
|
|
|
1537
1529
|
override fun latestTransactionIOS(sku: String): Promise<Variant_NullType_NitroPurchase> {
|
|
1538
1530
|
return Promise.async {
|
|
1539
|
-
throw OpenIapException(toErrorJson(
|
|
1531
|
+
throw OpenIapException(toErrorJson(OpenIapError.FeatureNotSupported()))
|
|
1540
1532
|
}
|
|
1541
1533
|
}
|
|
1542
1534
|
|
|
1543
1535
|
override fun getPendingTransactionsIOS(): Promise<Array<NitroPurchase>> {
|
|
1544
1536
|
return Promise.async {
|
|
1545
|
-
throw OpenIapException(toErrorJson(
|
|
1537
|
+
throw OpenIapException(toErrorJson(OpenIapError.FeatureNotSupported()))
|
|
1546
1538
|
}
|
|
1547
1539
|
}
|
|
1548
1540
|
|
|
1541
|
+
override fun getAllTransactionsIOS(): Promise<Array<NitroPurchase>> {
|
|
1542
|
+
return Promise.async {
|
|
1543
|
+
throw OpenIapException(toErrorJson(OpenIapError.FeatureNotSupported()))
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1549
1547
|
override fun syncIOS(): Promise<Boolean> {
|
|
1550
1548
|
return Promise.async {
|
|
1551
|
-
throw OpenIapException(toErrorJson(
|
|
1549
|
+
throw OpenIapException(toErrorJson(OpenIapError.FeatureNotSupported()))
|
|
1552
1550
|
}
|
|
1553
1551
|
}
|
|
1554
1552
|
|
|
@@ -1556,37 +1554,37 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
1556
1554
|
|
|
1557
1555
|
override fun isEligibleForIntroOfferIOS(groupID: String): Promise<Boolean> {
|
|
1558
1556
|
return Promise.async {
|
|
1559
|
-
throw OpenIapException(toErrorJson(
|
|
1557
|
+
throw OpenIapException(toErrorJson(OpenIapError.FeatureNotSupported()))
|
|
1560
1558
|
}
|
|
1561
1559
|
}
|
|
1562
1560
|
|
|
1563
1561
|
override fun getReceiptDataIOS(): Promise<String> {
|
|
1564
1562
|
return Promise.async {
|
|
1565
|
-
throw OpenIapException(toErrorJson(
|
|
1563
|
+
throw OpenIapException(toErrorJson(OpenIapError.FeatureNotSupported()))
|
|
1566
1564
|
}
|
|
1567
1565
|
}
|
|
1568
1566
|
|
|
1569
1567
|
override fun getReceiptIOS(): Promise<String> {
|
|
1570
1568
|
return Promise.async {
|
|
1571
|
-
throw OpenIapException(toErrorJson(
|
|
1569
|
+
throw OpenIapException(toErrorJson(OpenIapError.FeatureNotSupported()))
|
|
1572
1570
|
}
|
|
1573
1571
|
}
|
|
1574
1572
|
|
|
1575
1573
|
override fun requestReceiptRefreshIOS(): Promise<String> {
|
|
1576
1574
|
return Promise.async {
|
|
1577
|
-
throw OpenIapException(toErrorJson(
|
|
1575
|
+
throw OpenIapException(toErrorJson(OpenIapError.FeatureNotSupported()))
|
|
1578
1576
|
}
|
|
1579
1577
|
}
|
|
1580
1578
|
|
|
1581
1579
|
override fun isTransactionVerifiedIOS(sku: String): Promise<Boolean> {
|
|
1582
1580
|
return Promise.async {
|
|
1583
|
-
throw OpenIapException(toErrorJson(
|
|
1581
|
+
throw OpenIapException(toErrorJson(OpenIapError.FeatureNotSupported()))
|
|
1584
1582
|
}
|
|
1585
1583
|
}
|
|
1586
1584
|
|
|
1587
1585
|
override fun getTransactionJwsIOS(sku: String): Promise<Variant_NullType_String> {
|
|
1588
1586
|
return Promise.async {
|
|
1589
|
-
throw OpenIapException(toErrorJson(
|
|
1587
|
+
throw OpenIapException(toErrorJson(OpenIapError.FeatureNotSupported()))
|
|
1590
1588
|
}
|
|
1591
1589
|
}
|
|
1592
1590
|
|
|
@@ -1616,7 +1614,7 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
1616
1614
|
RnIapLog.payload("showAlternativeBillingDialogAndroid", null)
|
|
1617
1615
|
try {
|
|
1618
1616
|
val activity = context.currentActivity
|
|
1619
|
-
?: throw OpenIapException(toErrorJson(
|
|
1617
|
+
?: throw OpenIapException(toErrorJson(OpenIapError.DeveloperError(), debugMessage = "Activity not available"))
|
|
1620
1618
|
|
|
1621
1619
|
val userAccepted = withContext(Dispatchers.Main) {
|
|
1622
1620
|
openIap.setActivity(activity)
|
|
@@ -1813,7 +1811,7 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
1813
1811
|
|
|
1814
1812
|
val activity = withContext(Dispatchers.Main) {
|
|
1815
1813
|
runCatching { context.currentActivity }.getOrNull()
|
|
1816
|
-
} ?: throw OpenIapException(toErrorJson(
|
|
1814
|
+
} ?: throw OpenIapException(toErrorJson(OpenIapError.DeveloperError(), debugMessage = "Activity not available"))
|
|
1817
1815
|
|
|
1818
1816
|
val openIapParams = OpenIapLaunchExternalLinkParams(
|
|
1819
1817
|
billingProgram = mapBillingProgram(params.billingProgram),
|
|
@@ -1868,96 +1866,98 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
1868
1866
|
|
|
1869
1867
|
override fun canPresentExternalPurchaseNoticeIOS(): Promise<Boolean> {
|
|
1870
1868
|
return Promise.async {
|
|
1871
|
-
throw OpenIapException(toErrorJson(
|
|
1869
|
+
throw OpenIapException(toErrorJson(OpenIapError.FeatureNotSupported()))
|
|
1872
1870
|
}
|
|
1873
1871
|
}
|
|
1874
1872
|
|
|
1875
1873
|
override fun presentExternalPurchaseNoticeSheetIOS(): Promise<ExternalPurchaseNoticeResultIOS> {
|
|
1876
1874
|
return Promise.async {
|
|
1877
|
-
throw OpenIapException(toErrorJson(
|
|
1875
|
+
throw OpenIapException(toErrorJson(OpenIapError.FeatureNotSupported()))
|
|
1878
1876
|
}
|
|
1879
1877
|
}
|
|
1880
1878
|
|
|
1881
1879
|
override fun presentExternalPurchaseLinkIOS(url: String): Promise<ExternalPurchaseLinkResultIOS> {
|
|
1882
1880
|
return Promise.async {
|
|
1883
|
-
throw OpenIapException(toErrorJson(
|
|
1881
|
+
throw OpenIapException(toErrorJson(OpenIapError.FeatureNotSupported()))
|
|
1884
1882
|
}
|
|
1885
1883
|
}
|
|
1886
1884
|
|
|
1887
1885
|
// ExternalPurchaseCustomLink (iOS 18.1+) - iOS only stubs
|
|
1888
1886
|
override fun isEligibleForExternalPurchaseCustomLinkIOS(): Promise<Boolean> {
|
|
1889
1887
|
return Promise.async {
|
|
1890
|
-
throw OpenIapException(toErrorJson(
|
|
1888
|
+
throw OpenIapException(toErrorJson(OpenIapError.FeatureNotSupported()))
|
|
1891
1889
|
}
|
|
1892
1890
|
}
|
|
1893
1891
|
|
|
1894
1892
|
override fun getExternalPurchaseCustomLinkTokenIOS(tokenType: ExternalPurchaseCustomLinkTokenTypeIOS): Promise<ExternalPurchaseCustomLinkTokenResultIOS> {
|
|
1895
1893
|
return Promise.async {
|
|
1896
|
-
throw OpenIapException(toErrorJson(
|
|
1894
|
+
throw OpenIapException(toErrorJson(OpenIapError.FeatureNotSupported()))
|
|
1897
1895
|
}
|
|
1898
1896
|
}
|
|
1899
1897
|
|
|
1900
1898
|
override fun showExternalPurchaseCustomLinkNoticeIOS(noticeType: ExternalPurchaseCustomLinkNoticeTypeIOS): Promise<ExternalPurchaseCustomLinkNoticeResultIOS> {
|
|
1901
1899
|
return Promise.async {
|
|
1902
|
-
throw OpenIapException(toErrorJson(
|
|
1900
|
+
throw OpenIapException(toErrorJson(OpenIapError.FeatureNotSupported()))
|
|
1903
1901
|
}
|
|
1904
1902
|
}
|
|
1905
1903
|
|
|
1906
1904
|
// ---------------------------------------------------------------------
|
|
1907
1905
|
// OpenIAP error helpers: unify error codes/messages from library
|
|
1908
1906
|
// ---------------------------------------------------------------------
|
|
1909
|
-
private fun parseOpenIapError(err: Throwable):
|
|
1910
|
-
// Try to extract
|
|
1907
|
+
private fun parseOpenIapError(err: Throwable): OpenIapError {
|
|
1908
|
+
// Try to extract OpenIapError from the exception chain
|
|
1911
1909
|
var cause: Throwable? = err
|
|
1912
1910
|
while (cause != null) {
|
|
1913
1911
|
val message = cause.message ?: ""
|
|
1914
1912
|
// Check if message contains OpenIAP error patterns
|
|
1915
1913
|
when {
|
|
1916
1914
|
message.contains("not prepared", ignoreCase = true) ||
|
|
1917
|
-
message.contains("not initialized", ignoreCase = true) -> return
|
|
1915
|
+
message.contains("not initialized", ignoreCase = true) -> return OpenIapError.NotPrepared
|
|
1918
1916
|
message.contains("developer error", ignoreCase = true) ||
|
|
1919
|
-
message.contains("activity not available", ignoreCase = true) -> return
|
|
1920
|
-
message.contains("network", ignoreCase = true) -> return
|
|
1917
|
+
message.contains("activity not available", ignoreCase = true) -> return OpenIapError.DeveloperError()
|
|
1918
|
+
message.contains("network", ignoreCase = true) -> return OpenIapError.NetworkError
|
|
1921
1919
|
message.contains("service unavailable", ignoreCase = true) ||
|
|
1922
|
-
message.contains("billing unavailable", ignoreCase = true) -> return
|
|
1920
|
+
message.contains("billing unavailable", ignoreCase = true) -> return OpenIapError.ServiceUnavailable()
|
|
1923
1921
|
}
|
|
1924
1922
|
cause = cause.cause
|
|
1925
1923
|
}
|
|
1926
1924
|
// Default to ServiceUnavailable if we can't determine the error type
|
|
1927
|
-
return
|
|
1925
|
+
return OpenIapError.ServiceUnavailable()
|
|
1928
1926
|
}
|
|
1929
1927
|
|
|
1930
1928
|
private fun toErrorJson(
|
|
1931
|
-
error:
|
|
1929
|
+
error: OpenIapError,
|
|
1932
1930
|
productId: String? = null,
|
|
1933
1931
|
debugMessage: String? = null,
|
|
1934
1932
|
messageOverride: String? = null
|
|
1935
1933
|
): String {
|
|
1936
|
-
val code =
|
|
1934
|
+
val code = OpenIapError.Companion.toCode(error)
|
|
1937
1935
|
val message = messageOverride?.takeIf { it.isNotBlank() }
|
|
1938
1936
|
?: error.message?.takeIf { it.isNotBlank() }
|
|
1939
|
-
?:
|
|
1940
|
-
|
|
1941
|
-
val
|
|
1937
|
+
?: OpenIapError.Companion.defaultMessage(code)
|
|
1938
|
+
val diagnostics = error.toJSON()
|
|
1939
|
+
val responseCode = (diagnostics["responseCode"] as? Number)?.toInt()
|
|
1940
|
+
val productIds = diagnostics["productIds"] as? List<*>
|
|
1941
|
+
val productType = diagnostics["productType"] as? String
|
|
1942
|
+
val isEmptyProductList = diagnostics["isEmptyProductList"] as? Boolean
|
|
1943
|
+
|
|
1944
|
+
val errorMap = mutableMapOf<String, Any?>(
|
|
1942
1945
|
"code" to code,
|
|
1943
1946
|
"message" to message
|
|
1944
1947
|
)
|
|
1945
1948
|
|
|
1946
|
-
errorMap["responseCode"] = -1
|
|
1947
|
-
debugMessage
|
|
1949
|
+
errorMap["responseCode"] = responseCode ?: -1
|
|
1950
|
+
debugMessage
|
|
1951
|
+
?.let { errorMap["debugMessage"] = it }
|
|
1952
|
+
?: (diagnostics["debugMessage"] as? String)?.let { errorMap["debugMessage"] = it }
|
|
1953
|
+
?: error.message?.let { errorMap["debugMessage"] = it }
|
|
1948
1954
|
productId?.let { errorMap["productId"] = it }
|
|
1955
|
+
if (!productIds.isNullOrEmpty()) errorMap["productIds"] = productIds
|
|
1956
|
+
productType?.let { errorMap["productType"] = it }
|
|
1957
|
+
isEmptyProductList?.let { errorMap["isEmptyProductList"] = it }
|
|
1949
1958
|
|
|
1950
1959
|
return try {
|
|
1951
|
-
|
|
1952
|
-
val valueStr = when (value) {
|
|
1953
|
-
is String -> "\"${value.replace("\"", "\\\"")}\""
|
|
1954
|
-
is Number -> value.toString()
|
|
1955
|
-
is Boolean -> value.toString()
|
|
1956
|
-
else -> "\"$value\""
|
|
1957
|
-
}
|
|
1958
|
-
"\"$key\":$valueStr"
|
|
1959
|
-
}
|
|
1960
|
-
"{${jsonPairs.joinToString(",")}}"
|
|
1960
|
+
JSONObject(errorMap).toString()
|
|
1961
1961
|
} catch (e: Exception) {
|
|
1962
1962
|
"$code: $message"
|
|
1963
1963
|
}
|
|
@@ -2011,18 +2011,21 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
2011
2011
|
}
|
|
2012
2012
|
|
|
2013
2013
|
private fun toErrorResult(
|
|
2014
|
-
error:
|
|
2014
|
+
error: OpenIapError,
|
|
2015
2015
|
productId: String? = null,
|
|
2016
2016
|
debugMessage: String? = null,
|
|
2017
2017
|
messageOverride: String? = null
|
|
2018
2018
|
): NitroPurchaseResult {
|
|
2019
|
-
val code =
|
|
2019
|
+
val code = OpenIapError.Companion.toCode(error)
|
|
2020
2020
|
val message = messageOverride?.takeIf { it.isNotBlank() }
|
|
2021
2021
|
?: error.message?.takeIf { it.isNotBlank() }
|
|
2022
|
-
?:
|
|
2022
|
+
?: OpenIapError.Companion.defaultMessage(code)
|
|
2023
|
+
val diagnostics = error.toJSON()
|
|
2024
|
+
val responseCode = (diagnostics["responseCode"] as? Number)?.toDouble()
|
|
2025
|
+
val diagnosticMessage = diagnostics["debugMessage"] as? String
|
|
2023
2026
|
return NitroPurchaseResult(
|
|
2024
|
-
responseCode = -1.0,
|
|
2025
|
-
debugMessage = debugMessage ?: error.message,
|
|
2027
|
+
responseCode = responseCode ?: -1.0,
|
|
2028
|
+
debugMessage = debugMessage ?: diagnosticMessage ?: error.message,
|
|
2026
2029
|
code = code,
|
|
2027
2030
|
message = message,
|
|
2028
2031
|
purchaseToken = null
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
package com.margelo.nitro.iap
|
|
2
|
+
|
|
3
|
+
import dev.hyo.openiap.ProductCommon
|
|
4
|
+
import dev.hyo.openiap.ProductQueryType
|
|
5
|
+
import kotlin.coroutines.cancellation.CancellationException
|
|
6
|
+
import kotlinx.coroutines.async
|
|
7
|
+
import kotlinx.coroutines.coroutineScope
|
|
8
|
+
|
|
9
|
+
internal suspend fun collectAllQueryProducts(
|
|
10
|
+
skusList: List<String>,
|
|
11
|
+
fetchKind: suspend (ProductQueryType) -> List<ProductCommon>,
|
|
12
|
+
onFailure: (ProductQueryType, Throwable) -> Unit = { _, _ -> },
|
|
13
|
+
): List<ProductCommon> = coroutineScope {
|
|
14
|
+
val byId = linkedMapOf<String, ProductCommon>()
|
|
15
|
+
var firstFailure: Throwable? = null
|
|
16
|
+
|
|
17
|
+
val queries = listOf(ProductQueryType.InApp, ProductQueryType.Subs).map { kind ->
|
|
18
|
+
kind to async {
|
|
19
|
+
runCatching {
|
|
20
|
+
fetchKind(kind)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
queries.forEach { (kind, query) ->
|
|
26
|
+
query.await().onSuccess { fetched ->
|
|
27
|
+
fetched.forEach { product ->
|
|
28
|
+
byId.putIfAbsent(product.id, product)
|
|
29
|
+
}
|
|
30
|
+
}.onFailure { error ->
|
|
31
|
+
if (error is CancellationException) throw error
|
|
32
|
+
onFailure(kind, error)
|
|
33
|
+
if (firstFailure == null) firstFailure = error
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (byId.isEmpty()) {
|
|
38
|
+
firstFailure?.let { throw it }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
skusList.mapNotNull { byId[it] }
|
|
42
|
+
}
|