react-native-iap 16.2.4 → 16.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt +115 -15
- package/ios/HybridRnIap.swift +5 -1
- package/lib/module/index.js +57 -7
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.kepler.js +9 -0
- package/lib/module/index.kepler.js.map +1 -1
- package/lib/module/kit-api.js +3 -1
- package/lib/module/kit-api.js.map +1 -1
- package/lib/module/types.js +2 -0
- package/lib/module/types.js.map +1 -1
- package/lib/module/vega-adapter.js +9 -0
- package/lib/module/vega-adapter.js.map +1 -1
- package/lib/typescript/src/index.d.ts +3 -1
- 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/kit-api.d.ts +119 -2
- package/lib/typescript/src/kit-api.d.ts.map +1 -1
- package/lib/typescript/src/specs/RnIap.nitro.d.ts +13 -3
- package/lib/typescript/src/specs/RnIap.nitro.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +44 -9
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/vega-adapter.d.ts.map +1 -1
- package/nitrogen/generated/android/NitroIap+autolinking.cmake +1 -1
- package/nitrogen/generated/android/c++/JHybridRnIapSpec.cpp +8 -4
- package/nitrogen/generated/android/c++/JHybridRnIapSpec.hpp +1 -1
- package/nitrogen/generated/android/c++/JNitroPurchaseVerificationResultAndroid.hpp +5 -1
- package/nitrogen/generated/android/c++/JNitroPurchaseVerificationResultHorizon.hpp +69 -0
- package/nitrogen/generated/android/c++/JNitroVerifyPurchaseWithIapkitAmazonProps.hpp +5 -1
- package/nitrogen/generated/android/c++/JNitroVerifyPurchaseWithIapkitResult.hpp +5 -1
- package/nitrogen/generated/android/c++/JVariant_NitroPurchaseVerificationResultIOS_NitroPurchaseVerificationResultAndroid_NitroPurchaseVerificationResultHorizon.cpp +30 -0
- package/nitrogen/generated/android/c++/{JVariant_NitroPurchaseVerificationResultIOS_NitroPurchaseVerificationResultAndroid.hpp → JVariant_NitroPurchaseVerificationResultIOS_NitroPurchaseVerificationResultAndroid_NitroPurchaseVerificationResultHorizon.hpp} +33 -16
- package/nitrogen/generated/android/c++/JVariant_NullType_NitroVerifyPurchaseWithIapkitAmazonProps.hpp +1 -1
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/HybridRnIapSpec.kt +1 -1
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroPurchaseVerificationResultAndroid.kt +8 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroPurchaseVerificationResultHorizon.kt +61 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroVerifyPurchaseWithIapkitAmazonProps.kt +8 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroVerifyPurchaseWithIapkitResult.kt +7 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/{Variant_NitroPurchaseVerificationResultIOS_NitroPurchaseVerificationResultAndroid.kt → Variant_NitroPurchaseVerificationResultIOS_NitroPurchaseVerificationResultAndroid_NitroPurchaseVerificationResultHorizon.kt} +21 -8
- package/nitrogen/generated/ios/NitroIap-Swift-Cxx-Bridge.cpp +4 -4
- package/nitrogen/generated/ios/NitroIap-Swift-Cxx-Bridge.hpp +43 -34
- package/nitrogen/generated/ios/NitroIap-Swift-Cxx-Umbrella.hpp +3 -0
- package/nitrogen/generated/ios/c++/HybridRnIapSpecSwift.hpp +4 -1
- package/nitrogen/generated/ios/swift/{Func_void_std__variant_NitroPurchaseVerificationResultIOS__NitroPurchaseVerificationResultAndroid_.swift → Func_void_std__variant_NitroPurchaseVerificationResultIOS__NitroPurchaseVerificationResultAndroid__NitroPurchaseVerificationResultHorizon_.swift} +14 -11
- package/nitrogen/generated/ios/swift/HybridRnIapSpec.swift +1 -1
- package/nitrogen/generated/ios/swift/HybridRnIapSpec_cxx.swift +11 -9
- package/nitrogen/generated/ios/swift/NitroPurchaseVerificationResultAndroid.swift +7 -2
- package/nitrogen/generated/ios/swift/NitroPurchaseVerificationResultHorizon.swift +71 -0
- package/nitrogen/generated/ios/swift/NitroVerifyPurchaseWithIapkitAmazonProps.swift +39 -2
- package/nitrogen/generated/ios/swift/NitroVerifyPurchaseWithIapkitResult.swift +38 -1
- package/nitrogen/generated/ios/swift/{Variant_NitroPurchaseVerificationResultIOS_NitroPurchaseVerificationResultAndroid.swift → Variant_NitroPurchaseVerificationResultIOS_NitroPurchaseVerificationResultAndroid_NitroPurchaseVerificationResultHorizon.swift} +6 -4
- package/nitrogen/generated/shared/c++/HybridRnIapSpec.hpp +4 -1
- package/nitrogen/generated/shared/c++/NitroPurchaseVerificationResultAndroid.hpp +5 -1
- package/nitrogen/generated/shared/c++/NitroPurchaseVerificationResultHorizon.hpp +93 -0
- package/nitrogen/generated/shared/c++/NitroVerifyPurchaseWithIapkitAmazonProps.hpp +6 -2
- package/nitrogen/generated/shared/c++/NitroVerifyPurchaseWithIapkitResult.hpp +5 -1
- package/openiap-versions.json +3 -3
- package/package.json +1 -1
- package/src/index.kepler.ts +10 -2
- package/src/index.ts +94 -19
- package/src/kit-api.ts +125 -4
- package/src/specs/RnIap.nitro.ts +16 -2
- package/src/types.ts +45 -9
- package/src/vega-adapter.ts +10 -0
- package/nitrogen/generated/android/c++/JVariant_NitroPurchaseVerificationResultIOS_NitroPurchaseVerificationResultAndroid.cpp +0 -26
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
**React Native IAP** is a high-performance in-app purchase library using Nitro Modules that **conforms to the [Open IAP specification](https://openiap.dev)**. It provides a unified API for handling in-app purchases across iOS and Android platforms with comprehensive error handling and modern TypeScript support.
|
|
9
9
|
|
|
10
|
-
<a href="https://openiap.dev"><img src="https://raw.githubusercontent.com/hyodotdev/openiap/main/logo.
|
|
10
|
+
<a href="https://openiap.dev"><img src="https://raw.githubusercontent.com/hyodotdev/openiap/main/logo.webp" alt="Open IAP" height="40" /></a>
|
|
11
11
|
</div>
|
|
12
12
|
|
|
13
13
|
## 📚 Documentation
|
|
@@ -137,7 +137,7 @@ Quick links:
|
|
|
137
137
|
|
|
138
138
|
## Powered by OpenIAP
|
|
139
139
|
|
|
140
|
-
<a href="https://openiap.dev"><img src="https://raw.githubusercontent.com/hyodotdev/openiap/main/logo.
|
|
140
|
+
<a href="https://openiap.dev"><img src="https://raw.githubusercontent.com/hyodotdev/openiap/main/logo.webp" alt="OpenIAP" height="50" /></a>
|
|
141
141
|
|
|
142
142
|
React Native IAP conforms to the **[OpenIAP specification](https://openiap.dev)** — an open, vendor-neutral interoperability standard for in-app purchases. OpenIAP provides:
|
|
143
143
|
|
|
@@ -4,7 +4,7 @@ import com.facebook.react.bridge.ReactApplicationContext
|
|
|
4
4
|
import com.margelo.nitro.NitroModules
|
|
5
5
|
import com.margelo.nitro.core.NullType
|
|
6
6
|
import com.margelo.nitro.core.Promise
|
|
7
|
-
import dev.hyo.openiap.AndroidSubscriptionOfferInput
|
|
7
|
+
import dev.hyo.openiap.AndroidSubscriptionOfferInput as OpenIapAndroidSubscriptionOfferInput
|
|
8
8
|
import dev.hyo.openiap.DeepLinkOptions as OpenIapDeepLinkOptions
|
|
9
9
|
import dev.hyo.openiap.FetchProductsResult
|
|
10
10
|
import dev.hyo.openiap.FetchProductsResultAll
|
|
@@ -31,8 +31,10 @@ import dev.hyo.openiap.SubResponseCodeAndroid as OpenIapSubResponseCodeAndroid
|
|
|
31
31
|
import dev.hyo.openiap.SubscriptionProductReplacementParamsAndroid as OpenIapSubscriptionProductReplacementParams
|
|
32
32
|
import dev.hyo.openiap.SubscriptionReplacementModeAndroid as OpenIapSubscriptionReplacementMode
|
|
33
33
|
import dev.hyo.openiap.VerifyPurchaseGoogleOptions
|
|
34
|
+
import dev.hyo.openiap.VerifyPurchaseHorizonOptions
|
|
34
35
|
import dev.hyo.openiap.VerifyPurchaseProps
|
|
35
36
|
import dev.hyo.openiap.VerifyPurchaseResultAndroid
|
|
37
|
+
import dev.hyo.openiap.VerifyPurchaseResultHorizon
|
|
36
38
|
import dev.hyo.openiap.InitConnectionConfig as OpenIapInitConnectionConfig
|
|
37
39
|
import dev.hyo.openiap.listener.OpenIapPurchaseErrorListener
|
|
38
40
|
import dev.hyo.openiap.listener.OpenIapPurchaseUpdateListener
|
|
@@ -99,6 +101,44 @@ internal fun <T> removeSingletonBridgeListener(
|
|
|
99
101
|
}
|
|
100
102
|
}
|
|
101
103
|
|
|
104
|
+
internal fun requireValidSubscriptionOffers(
|
|
105
|
+
offers: Array<AndroidSubscriptionOfferInput>?,
|
|
106
|
+
): List<OpenIapAndroidSubscriptionOfferInput> {
|
|
107
|
+
require(offers.orEmpty().all { it.sku.isNotBlank() && it.offerToken.isNotBlank() }) {
|
|
108
|
+
"Every subscription offer must include non-empty sku and offerToken strings"
|
|
109
|
+
}
|
|
110
|
+
return offers.orEmpty().map { offer ->
|
|
111
|
+
OpenIapAndroidSubscriptionOfferInput(
|
|
112
|
+
sku = offer.sku,
|
|
113
|
+
offerToken = offer.offerToken,
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
internal fun requireMatchingPurchaseOptions(
|
|
119
|
+
type: ProductQueryType,
|
|
120
|
+
hasSubscriptionOffers: Boolean,
|
|
121
|
+
hasSubscriptionReplacementParams: Boolean,
|
|
122
|
+
purchaseToken: String?,
|
|
123
|
+
originalExternalTransactionId: String?,
|
|
124
|
+
offerToken: String?,
|
|
125
|
+
) {
|
|
126
|
+
when (type) {
|
|
127
|
+
ProductQueryType.InApp -> require(
|
|
128
|
+
!hasSubscriptionOffers &&
|
|
129
|
+
!hasSubscriptionReplacementParams &&
|
|
130
|
+
purchaseToken == null &&
|
|
131
|
+
originalExternalTransactionId == null,
|
|
132
|
+
) { "Subscription options require product type subs" }
|
|
133
|
+
ProductQueryType.Subs -> require(offerToken == null) {
|
|
134
|
+
"offerToken requires product type in-app"
|
|
135
|
+
}
|
|
136
|
+
ProductQueryType.All -> throw IllegalArgumentException(
|
|
137
|
+
"Product type all is only supported for product queries"
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
102
142
|
/**
|
|
103
143
|
* Bound for the pending purchase-event queues, matching expo-iap's
|
|
104
144
|
* ExpoIapHelper.MAX_BUFFERED_EVENTS. Oldest entries are dropped on overflow.
|
|
@@ -555,17 +595,37 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
555
595
|
val typeHint = androidRequest.skus.firstOrNull()?.let { productTypeBySku[it] } ?: "in-app"
|
|
556
596
|
val queryType = parseProductQueryType(typeHint)
|
|
557
597
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
598
|
+
try {
|
|
599
|
+
requireMatchingPurchaseOptions(
|
|
600
|
+
type = queryType,
|
|
601
|
+
hasSubscriptionOffers = androidRequest.subscriptionOffers is
|
|
602
|
+
Variant_NullType_Array_AndroidSubscriptionOfferInput_.Second,
|
|
603
|
+
hasSubscriptionReplacementParams =
|
|
604
|
+
androidRequest.subscriptionProductReplacementParams is
|
|
605
|
+
Variant_NullType_SubscriptionProductReplacementParamsAndroid.Second,
|
|
606
|
+
purchaseToken = androidRequest.purchaseToken.unwrapString(),
|
|
607
|
+
originalExternalTransactionId =
|
|
608
|
+
androidRequest.originalExternalTransactionId.unwrapString(),
|
|
609
|
+
offerToken = androidRequest.offerToken.unwrapString(),
|
|
610
|
+
)
|
|
611
|
+
} catch (error: IllegalArgumentException) {
|
|
612
|
+
sendPurchaseError(
|
|
613
|
+
toErrorResult(OpenIapError.DeveloperError(error.message))
|
|
614
|
+
)
|
|
615
|
+
return@async defaultResult
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
val subscriptionOffers = try {
|
|
619
|
+
requireValidSubscriptionOffers(
|
|
620
|
+
(androidRequest.subscriptionOffers as?
|
|
621
|
+
Variant_NullType_Array_AndroidSubscriptionOfferInput_.Second)?.value,
|
|
622
|
+
)
|
|
623
|
+
} catch (error: IllegalArgumentException) {
|
|
624
|
+
sendPurchaseError(
|
|
625
|
+
toErrorResult(OpenIapError.DeveloperError(error.message))
|
|
626
|
+
)
|
|
627
|
+
return@async defaultResult
|
|
628
|
+
}
|
|
569
629
|
val normalizedOffers = subscriptionOffers.takeIf { it.isNotEmpty() }
|
|
570
630
|
val developerBillingOption =
|
|
571
631
|
(androidRequest.developerBillingOption as?
|
|
@@ -1405,10 +1465,45 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
1405
1465
|
}
|
|
1406
1466
|
|
|
1407
1467
|
// Receipt validation - calls OpenIAP's verifyPurchase
|
|
1408
|
-
override fun verifyPurchase(params: NitroPurchaseVerificationParams): Promise<
|
|
1468
|
+
override fun verifyPurchase(params: NitroPurchaseVerificationParams): Promise<Variant_NitroPurchaseVerificationResultIOS_NitroPurchaseVerificationResultAndroid_NitroPurchaseVerificationResultHorizon> {
|
|
1409
1469
|
return Promise.async {
|
|
1410
1470
|
try {
|
|
1411
|
-
|
|
1471
|
+
val nitroHorizonOptions =
|
|
1472
|
+
(params.horizon as? Variant_NullType_NitroPurchaseVerificationHorizonOptions.Second)?.value
|
|
1473
|
+
if (nitroHorizonOptions != null) {
|
|
1474
|
+
val validations = mapOf(
|
|
1475
|
+
"horizon.sku" to nitroHorizonOptions.sku,
|
|
1476
|
+
"horizon.userId" to nitroHorizonOptions.userId,
|
|
1477
|
+
"horizon.accessToken" to nitroHorizonOptions.accessToken
|
|
1478
|
+
)
|
|
1479
|
+
for ((name, value) in validations) {
|
|
1480
|
+
if (value.isEmpty()) {
|
|
1481
|
+
throw OpenIapException(toErrorJson(OpenIapError.DeveloperError(), debugMessage = "Missing or empty required parameter: $name"))
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
RnIapLog.payload("verifyPurchase", mapOf(
|
|
1486
|
+
"store" to "horizon",
|
|
1487
|
+
"sku" to nitroHorizonOptions.sku
|
|
1488
|
+
))
|
|
1489
|
+
val props = VerifyPurchaseProps(
|
|
1490
|
+
horizon = VerifyPurchaseHorizonOptions(
|
|
1491
|
+
sku = nitroHorizonOptions.sku,
|
|
1492
|
+
userId = nitroHorizonOptions.userId,
|
|
1493
|
+
accessToken = nitroHorizonOptions.accessToken
|
|
1494
|
+
)
|
|
1495
|
+
)
|
|
1496
|
+
val horizonResult = openIap.verifyPurchase(props) as? VerifyPurchaseResultHorizon
|
|
1497
|
+
?: throw OpenIapException(toErrorJson(OpenIapError.InvalidPurchaseVerification, debugMessage = "Unexpected Horizon result type from verifyPurchase"))
|
|
1498
|
+
@Suppress("DEPRECATION")
|
|
1499
|
+
val result = NitroPurchaseVerificationResultHorizon(
|
|
1500
|
+
isValid = horizonResult.isValid,
|
|
1501
|
+
grantTime = horizonResult.grantTime.wrapVariant(),
|
|
1502
|
+
success = horizonResult.success
|
|
1503
|
+
)
|
|
1504
|
+
return@async Variant_NitroPurchaseVerificationResultIOS_NitroPurchaseVerificationResultAndroid_NitroPurchaseVerificationResultHorizon.Third(result)
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1412
1507
|
val nitroGoogleOptions = (params.google as? Variant_NullType_NitroPurchaseVerificationGoogleOptions.Second)?.value
|
|
1413
1508
|
?: throw OpenIapException(toErrorJson(OpenIapError.DeveloperError(), debugMessage = "Missing required parameter: google options"))
|
|
1414
1509
|
|
|
@@ -1459,6 +1554,7 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
1459
1554
|
|
|
1460
1555
|
// Convert OpenIAP result to Nitro result
|
|
1461
1556
|
val result = NitroPurchaseVerificationResultAndroid(
|
|
1557
|
+
isValid = androidResult.isValid,
|
|
1462
1558
|
autoRenewing = androidResult.autoRenewing,
|
|
1463
1559
|
betaProduct = androidResult.betaProduct,
|
|
1464
1560
|
cancelDate = androidResult.cancelDate.wrapVariant(),
|
|
@@ -1479,7 +1575,7 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
1479
1575
|
testTransaction = androidResult.testTransaction
|
|
1480
1576
|
)
|
|
1481
1577
|
|
|
1482
|
-
|
|
1578
|
+
Variant_NitroPurchaseVerificationResultIOS_NitroPurchaseVerificationResultAndroid_NitroPurchaseVerificationResultHorizon.Second(result)
|
|
1483
1579
|
|
|
1484
1580
|
} catch (e: OpenIapException) {
|
|
1485
1581
|
RnIapLog.failure("verifyPurchase", e)
|
|
@@ -1524,6 +1620,9 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
1524
1620
|
val amazonMap = mutableMapOf<String, Any?>(
|
|
1525
1621
|
"receiptId" to amazon.receiptId
|
|
1526
1622
|
)
|
|
1623
|
+
amazon.expectedProductId.unwrapString()?.let {
|
|
1624
|
+
amazonMap["expectedProductId"] = it
|
|
1625
|
+
}
|
|
1527
1626
|
amazon.userId.unwrapString()?.let { amazonMap["userId"] = it }
|
|
1528
1627
|
amazon.sandbox.unwrapBool()?.let { amazonMap["sandbox"] = it }
|
|
1529
1628
|
iapkitMap["amazon"] = amazonMap
|
|
@@ -1558,6 +1657,7 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
1558
1657
|
clientPayload = clientPayload?.let {
|
|
1559
1658
|
Variant_NullType_NitroIapkitProductClientPayload.Second(it)
|
|
1560
1659
|
},
|
|
1660
|
+
environment = item.environment?.let { Variant_NullType_String.Second(it) },
|
|
1561
1661
|
isValid = item.isValid,
|
|
1562
1662
|
productId = item.productId?.let { Variant_NullType_String.Second(it) },
|
|
1563
1663
|
// Use rawValue ("pending-acknowledgment"), not the Kotlin
|
package/ios/HybridRnIap.swift
CHANGED
|
@@ -399,7 +399,7 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
399
399
|
}
|
|
400
400
|
}
|
|
401
401
|
|
|
402
|
-
func verifyPurchase(params: NitroPurchaseVerificationParams) throws -> Promise<
|
|
402
|
+
func verifyPurchase(params: NitroPurchaseVerificationParams) throws -> Promise<Variant_NitroPurchaseVerificationResultIOS_NitroPurchaseVerificationResultAndroid_NitroPurchaseVerificationResultHorizon> {
|
|
403
403
|
return Promise.async {
|
|
404
404
|
do {
|
|
405
405
|
// Extract SKU from apple options (new platform-specific structure)
|
|
@@ -475,6 +475,9 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
475
475
|
var amazonDict: [String: Any] = [
|
|
476
476
|
"receiptId": amazon.receiptId
|
|
477
477
|
]
|
|
478
|
+
if case .second(let expectedProductId) = amazon.expectedProductId {
|
|
479
|
+
amazonDict["expectedProductId"] = expectedProductId
|
|
480
|
+
}
|
|
478
481
|
if case .second(let sandbox) = amazon.sandbox {
|
|
479
482
|
amazonDict["sandbox"] = sandbox
|
|
480
483
|
}
|
|
@@ -506,6 +509,7 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
506
509
|
}
|
|
507
510
|
nitroIapkitResult = NitroVerifyPurchaseWithIapkitResult(
|
|
508
511
|
clientPayload: clientPayload.map { .second($0) },
|
|
512
|
+
environment: RnIapHelper.wrapString(item.environment),
|
|
509
513
|
isValid: item.isValid,
|
|
510
514
|
productId: RnIapHelper.wrapString(item.productId),
|
|
511
515
|
state: IapkitPurchaseState(fromString: item.state.rawValue) ?? .unknown,
|
package/lib/module/index.js
CHANGED
|
@@ -621,7 +621,10 @@ export const fetchProducts = async request => {
|
|
|
621
621
|
} = request;
|
|
622
622
|
try {
|
|
623
623
|
if (!skus?.length) {
|
|
624
|
-
throw
|
|
624
|
+
throw createPurchaseError({
|
|
625
|
+
message: 'No SKUs provided',
|
|
626
|
+
code: ErrorCode.EmptySkuList
|
|
627
|
+
});
|
|
625
628
|
}
|
|
626
629
|
const normalizedType = normalizeProductQueryType(type);
|
|
627
630
|
const fetchAndConvert = async nitroType => {
|
|
@@ -1269,6 +1272,12 @@ export const requestPurchase = async request => {
|
|
|
1269
1272
|
type
|
|
1270
1273
|
} = request;
|
|
1271
1274
|
const normalizedType = normalizeProductQueryType(type ?? 'in-app');
|
|
1275
|
+
if (normalizedType === 'all') {
|
|
1276
|
+
throw createPurchaseError({
|
|
1277
|
+
code: ErrorCode.DeveloperError,
|
|
1278
|
+
message: 'Product type all is only supported for product queries.'
|
|
1279
|
+
});
|
|
1280
|
+
}
|
|
1272
1281
|
const isSubs = isSubscriptionQuery(normalizedType);
|
|
1273
1282
|
const perPlatformRequest = platformRequest;
|
|
1274
1283
|
if (!perPlatformRequest) {
|
|
@@ -1278,11 +1287,18 @@ export const requestPurchase = async request => {
|
|
|
1278
1287
|
const androidRequestSource = isAndroidStoreRuntime() ? perPlatformRequest.google : undefined;
|
|
1279
1288
|
if (Platform.OS === 'ios') {
|
|
1280
1289
|
if (!iosRequestSource?.sku) {
|
|
1281
|
-
throw
|
|
1290
|
+
throw createPurchaseError({
|
|
1291
|
+
message: 'Invalid request for iOS. The `sku` property is required.',
|
|
1292
|
+
code: ErrorCode.EmptySkuList
|
|
1293
|
+
});
|
|
1282
1294
|
}
|
|
1283
1295
|
} else if (isAndroidStoreRuntime()) {
|
|
1284
|
-
|
|
1285
|
-
|
|
1296
|
+
const skus = androidRequestSource?.skus;
|
|
1297
|
+
if (!Array.isArray(skus) || skus.length === 0 || skus.some(sku => typeof sku !== 'string' || sku.trim() === '')) {
|
|
1298
|
+
throw createPurchaseError({
|
|
1299
|
+
message: 'Invalid request for Android. The `skus` property must contain only non-empty strings.',
|
|
1300
|
+
code: ErrorCode.EmptySkuList
|
|
1301
|
+
});
|
|
1286
1302
|
}
|
|
1287
1303
|
} else {
|
|
1288
1304
|
throw unsupportedPlatformError();
|
|
@@ -1327,6 +1343,14 @@ export const requestPurchase = async request => {
|
|
|
1327
1343
|
unifiedRequest.apple = iosPayload;
|
|
1328
1344
|
}
|
|
1329
1345
|
if (isAndroidStoreRuntime() && androidRequestSource) {
|
|
1346
|
+
const rawAndroidRequest = androidRequestSource;
|
|
1347
|
+
const subscriptionOnlyFields = ['subscriptionOffers', 'subscriptionProductReplacementParams', 'purchaseToken', 'originalExternalTransactionId'];
|
|
1348
|
+
if (!isSubs && subscriptionOnlyFields.some(field => rawAndroidRequest[field] != null) || isSubs && rawAndroidRequest.offerToken != null) {
|
|
1349
|
+
throw createPurchaseError({
|
|
1350
|
+
message: 'Invalid request for Android. Purchase options must match the selected product type.',
|
|
1351
|
+
code: ErrorCode.DeveloperError
|
|
1352
|
+
});
|
|
1353
|
+
}
|
|
1330
1354
|
const androidRequest = isSubs ? androidRequestSource : androidRequestSource;
|
|
1331
1355
|
const androidPayload = {
|
|
1332
1356
|
skus: androidRequest.skus
|
|
@@ -1353,6 +1377,19 @@ export const requestPurchase = async request => {
|
|
|
1353
1377
|
}
|
|
1354
1378
|
if (isSubs) {
|
|
1355
1379
|
const subsRequest = androidRequest;
|
|
1380
|
+
const subscriptionOffers = subsRequest.subscriptionOffers;
|
|
1381
|
+
if (subscriptionOffers != null && (!Array.isArray(subscriptionOffers) || subscriptionOffers.some(offer => {
|
|
1382
|
+
if (offer == null || typeof offer !== 'object') {
|
|
1383
|
+
return true;
|
|
1384
|
+
}
|
|
1385
|
+
const candidate = offer;
|
|
1386
|
+
return typeof candidate.sku !== 'string' || candidate.sku.trim() === '' || typeof candidate.offerToken !== 'string' || candidate.offerToken.trim() === '';
|
|
1387
|
+
}))) {
|
|
1388
|
+
throw createPurchaseError({
|
|
1389
|
+
message: 'Invalid request for Android. Every subscription offer must include non-empty `sku` and `offerToken` strings.',
|
|
1390
|
+
code: ErrorCode.DeveloperError
|
|
1391
|
+
});
|
|
1392
|
+
}
|
|
1356
1393
|
if (subsRequest.purchaseToken) {
|
|
1357
1394
|
androidPayload.purchaseToken = subsRequest.purchaseToken;
|
|
1358
1395
|
}
|
|
@@ -1362,7 +1399,7 @@ export const requestPurchase = async request => {
|
|
|
1362
1399
|
if (subsRequest.subscriptionProductReplacementParams) {
|
|
1363
1400
|
androidPayload.subscriptionProductReplacementParams = subsRequest.subscriptionProductReplacementParams;
|
|
1364
1401
|
}
|
|
1365
|
-
androidPayload.subscriptionOffers = (
|
|
1402
|
+
androidPayload.subscriptionOffers = (subscriptionOffers ?? []).map(offer => ({
|
|
1366
1403
|
sku: offer.sku,
|
|
1367
1404
|
offerToken: offer.offerToken
|
|
1368
1405
|
}));
|
|
@@ -1642,10 +1679,18 @@ export const verifyPurchase = async options => {
|
|
|
1642
1679
|
latestTransaction: iosResult.latestTransaction ? convertNitroPurchaseToPurchase(iosResult.latestTransaction) : undefined
|
|
1643
1680
|
};
|
|
1644
1681
|
return result;
|
|
1682
|
+
} else if (params.horizon !== null) {
|
|
1683
|
+
const horizonResult = nitroResult;
|
|
1684
|
+
const result = {
|
|
1685
|
+
isValid: horizonResult.isValid,
|
|
1686
|
+
grantTime: horizonResult.grantTime,
|
|
1687
|
+
success: horizonResult.success
|
|
1688
|
+
};
|
|
1689
|
+
return result;
|
|
1645
1690
|
} else {
|
|
1646
|
-
// Android
|
|
1647
1691
|
const androidResult = nitroResult;
|
|
1648
1692
|
const result = {
|
|
1693
|
+
isValid: androidResult.isValid,
|
|
1649
1694
|
autoRenewing: androidResult.autoRenewing,
|
|
1650
1695
|
betaProduct: androidResult.betaProduct,
|
|
1651
1696
|
cancelDate: androidResult.cancelDate,
|
|
@@ -1697,9 +1742,11 @@ export const verifyPurchase = async options => {
|
|
|
1697
1742
|
* // apple: { jws: purchase.purchaseToken },
|
|
1698
1743
|
* // google: { purchaseToken: purchase.purchaseToken },
|
|
1699
1744
|
* amazon: {
|
|
1745
|
+
* expectedProductId: purchase.productId,
|
|
1700
1746
|
* userId: amazonUserId,
|
|
1701
1747
|
* receiptId: purchase.purchaseToken,
|
|
1702
|
-
*
|
|
1748
|
+
* // Enable only for App Tester after the IAPKit project opt-in.
|
|
1749
|
+
* sandbox: amazonSandboxEnabled,
|
|
1703
1750
|
* },
|
|
1704
1751
|
* },
|
|
1705
1752
|
* });
|
|
@@ -1726,6 +1773,9 @@ export const verifyPurchaseWithProvider = async options => {
|
|
|
1726
1773
|
...(result.iapkit.clientPayload == null ? {} : {
|
|
1727
1774
|
clientPayload: result.iapkit.clientPayload
|
|
1728
1775
|
}),
|
|
1776
|
+
...(result.iapkit.environment == null ? {} : {
|
|
1777
|
+
environment: result.iapkit.environment
|
|
1778
|
+
}),
|
|
1729
1779
|
isValid: result.iapkit.isValid,
|
|
1730
1780
|
...(result.iapkit.productId == null ? {} : {
|
|
1731
1781
|
productId: result.iapkit.productId
|