react-native-iap 14.3.8 → 14.4.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/NitroIap.podspec +11 -1
- package/README.md +2 -3
- package/android/build.gradle +24 -1
- package/android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt +374 -119
- package/android/src/main/java/com/margelo/nitro/iap/RnIapLog.kt +64 -0
- package/ios/HybridRnIap.swift +525 -362
- package/ios/RnIapHelper.swift +224 -0
- package/ios/RnIapLog.swift +127 -0
- package/lib/module/hooks/useIAP.js +2 -34
- package/lib/module/hooks/useIAP.js.map +1 -1
- package/lib/module/index.js +52 -2
- package/lib/module/index.js.map +1 -1
- package/lib/module/types.js.map +1 -1
- package/lib/typescript/plugin/src/withIAP.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useIAP.d.ts +0 -12
- package/lib/typescript/src/hooks/useIAP.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +3 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/specs/RnIap.nitro.d.ts +24 -0
- package/lib/typescript/src/specs/RnIap.nitro.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +8 -6
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/nitrogen/generated/android/c++/JHybridRnIapSpec.cpp +64 -0
- package/nitrogen/generated/android/c++/JHybridRnIapSpec.hpp +4 -0
- package/nitrogen/generated/android/c++/JIapPlatform.hpp +3 -3
- package/nitrogen/generated/android/c++/JPurchaseAndroid.hpp +6 -2
- package/nitrogen/generated/android/c++/JPurchaseIOS.hpp +4 -0
- package/nitrogen/generated/android/c++/JPurchaseState.hpp +6 -6
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/HybridRnIapSpec.kt +16 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/IapPlatform.kt +2 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/PurchaseAndroid.kt +4 -1
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/PurchaseIOS.kt +3 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/PurchaseState.kt +5 -5
- package/nitrogen/generated/ios/c++/HybridRnIapSpecSwift.hpp +32 -0
- package/nitrogen/generated/ios/swift/HybridRnIapSpec.swift +4 -0
- package/nitrogen/generated/ios/swift/HybridRnIapSpec_cxx.swift +82 -0
- package/nitrogen/generated/ios/swift/IapPlatform.swift +4 -4
- package/nitrogen/generated/ios/swift/PurchaseAndroid.swift +32 -2
- package/nitrogen/generated/ios/swift/PurchaseIOS.swift +13 -2
- package/nitrogen/generated/ios/swift/PurchaseState.swift +8 -8
- package/nitrogen/generated/shared/c++/HybridRnIapSpec.cpp +4 -0
- package/nitrogen/generated/shared/c++/HybridRnIapSpec.hpp +4 -0
- package/nitrogen/generated/shared/c++/IapPlatform.hpp +5 -5
- package/nitrogen/generated/shared/c++/PurchaseAndroid.hpp +6 -2
- package/nitrogen/generated/shared/c++/PurchaseIOS.hpp +5 -1
- package/nitrogen/generated/shared/c++/PurchaseState.hpp +11 -11
- package/openiap-versions.json +5 -0
- package/package.json +3 -2
- package/plugin/build/withIAP.js +35 -3
- package/plugin/src/withIAP.ts +44 -3
- package/src/hooks/useIAP.ts +3 -71
- package/src/index.ts +61 -2
- package/src/specs/RnIap.nitro.ts +28 -0
- package/src/types.ts +8 -6
|
@@ -1,27 +1,41 @@
|
|
|
1
1
|
package com.margelo.nitro.iap
|
|
2
2
|
|
|
3
|
-
import android.util.Log
|
|
4
3
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
5
4
|
import com.margelo.nitro.NitroModules
|
|
6
5
|
import com.margelo.nitro.core.Promise
|
|
6
|
+
import dev.hyo.openiap.AndroidSubscriptionOfferInput
|
|
7
|
+
import dev.hyo.openiap.DeepLinkOptions as OpenIapDeepLinkOptions
|
|
8
|
+
import dev.hyo.openiap.FetchProductsResult
|
|
9
|
+
import dev.hyo.openiap.FetchProductsResultProducts
|
|
10
|
+
import dev.hyo.openiap.FetchProductsResultSubscriptions
|
|
7
11
|
import dev.hyo.openiap.OpenIapError as OpenIAPError
|
|
8
12
|
import dev.hyo.openiap.OpenIapModule
|
|
13
|
+
import dev.hyo.openiap.ProductAndroid
|
|
14
|
+
import dev.hyo.openiap.ProductQueryType
|
|
15
|
+
import dev.hyo.openiap.ProductRequest
|
|
16
|
+
import dev.hyo.openiap.ProductSubscriptionAndroid
|
|
17
|
+
import dev.hyo.openiap.ProductSubscriptionAndroidOfferDetails
|
|
18
|
+
import dev.hyo.openiap.ProductCommon
|
|
19
|
+
import dev.hyo.openiap.ProductType
|
|
20
|
+
import dev.hyo.openiap.Purchase as OpenIapPurchase
|
|
21
|
+
import dev.hyo.openiap.PurchaseAndroid
|
|
22
|
+
import dev.hyo.openiap.RequestPurchaseAndroidProps
|
|
23
|
+
import dev.hyo.openiap.RequestPurchaseProps
|
|
24
|
+
import dev.hyo.openiap.RequestPurchasePropsByPlatforms
|
|
25
|
+
import dev.hyo.openiap.RequestPurchaseResultPurchase
|
|
26
|
+
import dev.hyo.openiap.RequestPurchaseResultPurchases
|
|
27
|
+
import dev.hyo.openiap.RequestSubscriptionAndroidProps
|
|
28
|
+
import dev.hyo.openiap.RequestSubscriptionPropsByPlatforms
|
|
9
29
|
import dev.hyo.openiap.listener.OpenIapPurchaseErrorListener
|
|
10
30
|
import dev.hyo.openiap.listener.OpenIapPurchaseUpdateListener
|
|
11
|
-
import dev.hyo.openiap.models.DeepLinkOptions as OpenIapDeepLinkOptions
|
|
12
|
-
import dev.hyo.openiap.models.OpenIapProduct
|
|
13
|
-
import dev.hyo.openiap.models.OpenIapPurchase
|
|
14
|
-
import dev.hyo.openiap.models.ProductRequest
|
|
15
|
-
import dev.hyo.openiap.models.OpenIapRequestPurchaseProps
|
|
16
|
-
import dev.hyo.openiap.models.OpenIapSerialization
|
|
17
31
|
import kotlinx.coroutines.Dispatchers
|
|
18
32
|
import kotlinx.coroutines.withContext
|
|
19
33
|
import kotlinx.coroutines.CompletableDeferred
|
|
34
|
+
import org.json.JSONArray
|
|
35
|
+
import org.json.JSONObject
|
|
36
|
+
import java.util.Locale
|
|
20
37
|
|
|
21
38
|
class HybridRnIap : HybridRnIapSpec() {
|
|
22
|
-
companion object {
|
|
23
|
-
const val TAG = "RnIap"
|
|
24
|
-
}
|
|
25
39
|
|
|
26
40
|
// Get ReactApplicationContext lazily from NitroModules
|
|
27
41
|
private val context: ReactApplicationContext by lazy {
|
|
@@ -44,8 +58,12 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
44
58
|
// Connection methods
|
|
45
59
|
override fun initConnection(): Promise<Boolean> {
|
|
46
60
|
return Promise.async {
|
|
61
|
+
RnIapLog.payload("initConnection", null)
|
|
47
62
|
// Fast-path: if already initialized, return immediately
|
|
48
|
-
if (isInitialized)
|
|
63
|
+
if (isInitialized) {
|
|
64
|
+
RnIapLog.result("initConnection", true)
|
|
65
|
+
return@async true
|
|
66
|
+
}
|
|
49
67
|
|
|
50
68
|
// Set current activity best-effort; don't fail init if missing
|
|
51
69
|
withContext(Dispatchers.Main) {
|
|
@@ -59,18 +77,32 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
59
77
|
false
|
|
60
78
|
} else true
|
|
61
79
|
}
|
|
62
|
-
if (wasExisting)
|
|
80
|
+
if (wasExisting) {
|
|
81
|
+
val result = initDeferred!!.await()
|
|
82
|
+
RnIapLog.result("initConnection.await", result)
|
|
83
|
+
return@async result
|
|
84
|
+
}
|
|
63
85
|
|
|
64
86
|
if (!listenersAttached) {
|
|
65
87
|
listenersAttached = true
|
|
88
|
+
RnIapLog.payload("listeners.attach", null)
|
|
66
89
|
openIap.addPurchaseUpdateListener(OpenIapPurchaseUpdateListener { p ->
|
|
67
|
-
runCatching {
|
|
68
|
-
.
|
|
90
|
+
runCatching {
|
|
91
|
+
RnIapLog.result(
|
|
92
|
+
"purchaseUpdatedListener",
|
|
93
|
+
mapOf("id" to p.id, "sku" to p.productId)
|
|
94
|
+
)
|
|
95
|
+
sendPurchaseUpdate(convertToNitroPurchase(p))
|
|
96
|
+
}.onFailure { RnIapLog.failure("purchaseUpdatedListener", it) }
|
|
69
97
|
})
|
|
70
98
|
openIap.addPurchaseErrorListener(OpenIapPurchaseErrorListener { e ->
|
|
71
99
|
val code = OpenIAPError.toCode(e)
|
|
72
100
|
val message = e.message ?: OpenIAPError.defaultMessage(code)
|
|
73
101
|
runCatching {
|
|
102
|
+
RnIapLog.result(
|
|
103
|
+
"purchaseErrorListener",
|
|
104
|
+
mapOf("code" to code, "message" to message)
|
|
105
|
+
)
|
|
74
106
|
sendPurchaseError(
|
|
75
107
|
NitroPurchaseResult(
|
|
76
108
|
responseCode = -1.0,
|
|
@@ -80,15 +112,22 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
80
112
|
purchaseToken = null
|
|
81
113
|
)
|
|
82
114
|
)
|
|
83
|
-
}.onFailure {
|
|
115
|
+
}.onFailure { RnIapLog.failure("purchaseErrorListener", it) }
|
|
84
116
|
})
|
|
117
|
+
RnIapLog.result("listeners.attach", "attached")
|
|
85
118
|
}
|
|
86
119
|
|
|
87
120
|
// We created it above; reuse the shared instance
|
|
88
121
|
val deferred = initDeferred!!
|
|
89
122
|
try {
|
|
90
|
-
val ok =
|
|
123
|
+
val ok = try {
|
|
124
|
+
RnIapLog.payload("initConnection.native", null)
|
|
125
|
+
withContext(Dispatchers.Main) {
|
|
126
|
+
openIap.initConnection()
|
|
127
|
+
}
|
|
128
|
+
} catch (err: Throwable) {
|
|
91
129
|
val error = OpenIAPError.InitConnection()
|
|
130
|
+
RnIapLog.failure("initConnection.native", err)
|
|
92
131
|
throw Exception(
|
|
93
132
|
toErrorJson(
|
|
94
133
|
error = error,
|
|
@@ -99,6 +138,7 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
99
138
|
}
|
|
100
139
|
if (!ok) {
|
|
101
140
|
val error = OpenIAPError.InitConnection()
|
|
141
|
+
RnIapLog.failure("initConnection.native", Exception(error.message))
|
|
102
142
|
throw Exception(
|
|
103
143
|
toErrorJson(
|
|
104
144
|
error = error,
|
|
@@ -108,11 +148,13 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
108
148
|
}
|
|
109
149
|
isInitialized = true
|
|
110
150
|
deferred.complete(true)
|
|
151
|
+
RnIapLog.result("initConnection", true)
|
|
111
152
|
true
|
|
112
153
|
} catch (e: Exception) {
|
|
113
154
|
// Complete exceptionally so all concurrent awaiters receive the same failure
|
|
114
155
|
if (!deferred.isCompleted) deferred.completeExceptionally(e)
|
|
115
156
|
isInitialized = false
|
|
157
|
+
RnIapLog.failure("initConnection", e)
|
|
116
158
|
throw e
|
|
117
159
|
} finally {
|
|
118
160
|
initDeferred = null
|
|
@@ -122,10 +164,12 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
122
164
|
|
|
123
165
|
override fun endConnection(): Promise<Boolean> {
|
|
124
166
|
return Promise.async {
|
|
167
|
+
RnIapLog.payload("endConnection", null)
|
|
125
168
|
runCatching { openIap.endConnection() }
|
|
126
169
|
productTypeBySku.clear()
|
|
127
170
|
isInitialized = false
|
|
128
171
|
initDeferred = null
|
|
172
|
+
RnIapLog.result("endConnection", true)
|
|
129
173
|
true
|
|
130
174
|
}
|
|
131
175
|
}
|
|
@@ -133,7 +177,13 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
133
177
|
// Product methods
|
|
134
178
|
override fun fetchProducts(skus: Array<String>, type: String): Promise<Array<NitroProduct>> {
|
|
135
179
|
return Promise.async {
|
|
136
|
-
|
|
180
|
+
RnIapLog.payload(
|
|
181
|
+
"fetchProducts",
|
|
182
|
+
mapOf(
|
|
183
|
+
"skus" to skus.toList(),
|
|
184
|
+
"type" to type
|
|
185
|
+
)
|
|
186
|
+
)
|
|
137
187
|
|
|
138
188
|
if (skus.isEmpty()) {
|
|
139
189
|
throw Exception(toErrorJson(OpenIAPError.EmptySkuList))
|
|
@@ -141,40 +191,46 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
141
191
|
|
|
142
192
|
initConnection().await()
|
|
143
193
|
|
|
144
|
-
val
|
|
194
|
+
val queryType = parseProductQueryType(type)
|
|
145
195
|
val skusList = skus.toList()
|
|
146
196
|
|
|
147
|
-
val products: List<
|
|
148
|
-
|
|
149
|
-
val collected =
|
|
150
|
-
listOf(
|
|
151
|
-
|
|
152
|
-
|
|
197
|
+
val products: List<ProductCommon> = when (queryType) {
|
|
198
|
+
ProductQueryType.All -> {
|
|
199
|
+
val collected = linkedMapOf<String, ProductCommon>()
|
|
200
|
+
listOf(ProductQueryType.InApp, ProductQueryType.Subs).forEach { kind ->
|
|
201
|
+
RnIapLog.payload(
|
|
202
|
+
"fetchProducts.native",
|
|
203
|
+
mapOf("skus" to skusList, "type" to kind.rawValue)
|
|
204
|
+
)
|
|
205
|
+
val fetched = openIap.fetchProducts(ProductRequest(skusList, kind)).productsOrEmpty()
|
|
206
|
+
RnIapLog.result(
|
|
207
|
+
"fetchProducts.native",
|
|
208
|
+
fetched.map { mapOf("id" to it.id, "type" to it.type.rawValue) }
|
|
209
|
+
)
|
|
153
210
|
fetched.forEach { collected[it.id] = it }
|
|
154
211
|
}
|
|
155
212
|
collected.values.toList()
|
|
156
213
|
}
|
|
157
|
-
"inapp", "in-app" -> {
|
|
158
|
-
if (normalizedType == "inapp") {
|
|
159
|
-
Log.w(TAG, "fetchProducts received legacy type 'inapp'; forwarding as 'in-app'")
|
|
160
|
-
}
|
|
161
|
-
val requestType = ProductRequest.ProductRequestType.fromString("in-app")
|
|
162
|
-
openIap.fetchProducts(ProductRequest(skusList, requestType))
|
|
163
|
-
}
|
|
164
|
-
"subs" -> {
|
|
165
|
-
val requestType = ProductRequest.ProductRequestType.fromString("subs")
|
|
166
|
-
openIap.fetchProducts(ProductRequest(skusList, requestType))
|
|
167
|
-
}
|
|
168
214
|
else -> {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
215
|
+
RnIapLog.payload(
|
|
216
|
+
"fetchProducts.native",
|
|
217
|
+
mapOf("skus" to skusList, "type" to queryType.rawValue)
|
|
218
|
+
)
|
|
219
|
+
val fetched = openIap.fetchProducts(ProductRequest(skusList, queryType)).productsOrEmpty()
|
|
220
|
+
RnIapLog.result(
|
|
221
|
+
"fetchProducts.native",
|
|
222
|
+
fetched.map { mapOf("id" to it.id, "type" to it.type.rawValue) }
|
|
223
|
+
)
|
|
224
|
+
fetched
|
|
172
225
|
}
|
|
173
226
|
}
|
|
174
227
|
|
|
175
|
-
|
|
176
|
-
products.forEach { p -> productTypeBySku[p.id] = p.type.value }
|
|
228
|
+
products.forEach { p -> productTypeBySku[p.id] = p.type.rawValue }
|
|
177
229
|
|
|
230
|
+
RnIapLog.result(
|
|
231
|
+
"fetchProducts",
|
|
232
|
+
products.map { mapOf("id" to it.id, "type" to it.type.rawValue) }
|
|
233
|
+
)
|
|
178
234
|
products.map { convertToNitroProduct(it) }.toTypedArray()
|
|
179
235
|
}
|
|
180
236
|
}
|
|
@@ -183,15 +239,24 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
183
239
|
// Purchase methods (Unified)
|
|
184
240
|
override fun requestPurchase(request: NitroPurchaseRequest): Promise<RequestPurchaseResult?> {
|
|
185
241
|
return Promise.async {
|
|
186
|
-
val defaultResult = RequestPurchaseResult.create(emptyArray())
|
|
242
|
+
val defaultResult = RequestPurchaseResult.create(emptyArray<com.margelo.nitro.iap.Purchase>())
|
|
243
|
+
|
|
244
|
+
RnIapLog.payload(
|
|
245
|
+
"requestPurchase",
|
|
246
|
+
mapOf(
|
|
247
|
+
"androidSkus" to (request.android?.skus?.toList() ?: emptyList()),
|
|
248
|
+
"hasIOS" to (request.ios != null)
|
|
249
|
+
)
|
|
250
|
+
)
|
|
187
251
|
|
|
188
252
|
val androidRequest = request.android ?: run {
|
|
189
|
-
|
|
253
|
+
RnIapLog.warn("requestPurchase called without android payload")
|
|
190
254
|
sendPurchaseError(toErrorResult(OpenIAPError.DeveloperError))
|
|
191
255
|
return@async defaultResult
|
|
192
256
|
}
|
|
193
257
|
|
|
194
258
|
if (androidRequest.skus.isEmpty()) {
|
|
259
|
+
RnIapLog.warn("requestPurchase received empty SKU list")
|
|
195
260
|
sendPurchaseError(toErrorResult(OpenIAPError.EmptySkuList))
|
|
196
261
|
return@async defaultResult
|
|
197
262
|
}
|
|
@@ -200,31 +265,102 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
200
265
|
initConnection().await()
|
|
201
266
|
withContext(Dispatchers.Main) { runCatching { openIap.setActivity(context.currentActivity) } }
|
|
202
267
|
|
|
203
|
-
val
|
|
204
|
-
if (
|
|
205
|
-
|
|
206
|
-
|
|
268
|
+
val missingSkus = androidRequest.skus.filterNot { productTypeBySku.containsKey(it) }
|
|
269
|
+
if (missingSkus.isNotEmpty()) {
|
|
270
|
+
missingSkus.forEach { sku ->
|
|
271
|
+
RnIapLog.warn("requestPurchase missing cached type for $sku; attempting fetch")
|
|
272
|
+
val fetched = runCatching {
|
|
273
|
+
openIap.fetchProducts(
|
|
274
|
+
ProductRequest(listOf(sku), ProductQueryType.All)
|
|
275
|
+
).productsOrEmpty()
|
|
276
|
+
}.getOrElse { error ->
|
|
277
|
+
RnIapLog.failure("requestPurchase.fetchMissing", error)
|
|
278
|
+
emptyList()
|
|
279
|
+
}
|
|
280
|
+
fetched.firstOrNull()?.let { productTypeBySku[it.id] = it.type.rawValue }
|
|
281
|
+
if (!productTypeBySku.containsKey(sku)) {
|
|
282
|
+
sendPurchaseError(toErrorResult(OpenIAPError.SkuNotFound(sku), sku))
|
|
283
|
+
return@async defaultResult
|
|
284
|
+
}
|
|
285
|
+
}
|
|
207
286
|
}
|
|
208
|
-
|
|
209
|
-
val
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
287
|
+
|
|
288
|
+
val typeHint = androidRequest.skus.firstOrNull()?.let { productTypeBySku[it] } ?: "inapp"
|
|
289
|
+
val queryType = parseProductQueryType(typeHint)
|
|
290
|
+
|
|
291
|
+
val subscriptionOffers = androidRequest.subscriptionOffers
|
|
292
|
+
?.mapNotNull { offer ->
|
|
293
|
+
val sku = offer.sku
|
|
294
|
+
val token = offer.offerToken
|
|
295
|
+
if (sku.isBlank() || token.isBlank()) {
|
|
296
|
+
null
|
|
297
|
+
} else {
|
|
298
|
+
AndroidSubscriptionOfferInput(sku = sku, offerToken = token)
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
?: emptyList()
|
|
302
|
+
val normalizedOffers = subscriptionOffers.takeIf { it.isNotEmpty() }
|
|
303
|
+
|
|
304
|
+
val requestProps = when (queryType) {
|
|
305
|
+
ProductQueryType.Subs -> {
|
|
306
|
+
val replacementMode = (androidRequest.replacementModeAndroid as? Number)?.toInt()
|
|
307
|
+
val androidProps = RequestSubscriptionAndroidProps(
|
|
308
|
+
isOfferPersonalized = androidRequest.isOfferPersonalized,
|
|
309
|
+
obfuscatedAccountIdAndroid = androidRequest.obfuscatedAccountIdAndroid,
|
|
310
|
+
obfuscatedProfileIdAndroid = androidRequest.obfuscatedProfileIdAndroid,
|
|
311
|
+
purchaseTokenAndroid = androidRequest.purchaseTokenAndroid,
|
|
312
|
+
replacementModeAndroid = replacementMode,
|
|
313
|
+
skus = androidRequest.skus.toList(),
|
|
314
|
+
subscriptionOffers = normalizedOffers
|
|
315
|
+
)
|
|
316
|
+
RequestPurchaseProps(
|
|
317
|
+
request = RequestPurchaseProps.Request.Subscription(
|
|
318
|
+
RequestSubscriptionPropsByPlatforms(android = androidProps)
|
|
319
|
+
),
|
|
320
|
+
type = ProductQueryType.Subs
|
|
321
|
+
)
|
|
322
|
+
}
|
|
323
|
+
ProductQueryType.InApp, ProductQueryType.All -> {
|
|
324
|
+
val androidProps = RequestPurchaseAndroidProps(
|
|
325
|
+
isOfferPersonalized = androidRequest.isOfferPersonalized,
|
|
326
|
+
obfuscatedAccountIdAndroid = androidRequest.obfuscatedAccountIdAndroid,
|
|
327
|
+
obfuscatedProfileIdAndroid = androidRequest.obfuscatedProfileIdAndroid,
|
|
328
|
+
skus = androidRequest.skus.toList()
|
|
329
|
+
)
|
|
330
|
+
RequestPurchaseProps(
|
|
331
|
+
request = RequestPurchaseProps.Request.Purchase(
|
|
332
|
+
RequestPurchasePropsByPlatforms(android = androidProps)
|
|
333
|
+
),
|
|
334
|
+
type = ProductQueryType.InApp
|
|
335
|
+
)
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
RnIapLog.payload(
|
|
340
|
+
"requestPurchase.native",
|
|
341
|
+
mapOf(
|
|
342
|
+
"skus" to androidRequest.skus.toList(),
|
|
343
|
+
"type" to requestProps.type.rawValue,
|
|
344
|
+
"offerCount" to (normalizedOffers?.size ?: 0)
|
|
345
|
+
)
|
|
219
346
|
)
|
|
220
347
|
|
|
221
|
-
result.
|
|
222
|
-
|
|
223
|
-
|
|
348
|
+
val result = withContext(Dispatchers.Main) {
|
|
349
|
+
openIap.requestPurchase(requestProps)
|
|
350
|
+
}
|
|
351
|
+
val purchases = result.purchasesOrEmpty()
|
|
352
|
+
purchases.forEach { p ->
|
|
353
|
+
runCatching {
|
|
354
|
+
RnIapLog.result(
|
|
355
|
+
"requestPurchase.native",
|
|
356
|
+
mapOf("id" to p.id, "sku" to p.productId)
|
|
357
|
+
)
|
|
358
|
+
}.onFailure { RnIapLog.failure("requestPurchase.native", it) }
|
|
224
359
|
}
|
|
225
360
|
|
|
226
361
|
defaultResult
|
|
227
362
|
} catch (e: Exception) {
|
|
363
|
+
RnIapLog.failure("requestPurchase", e)
|
|
228
364
|
sendPurchaseError(
|
|
229
365
|
toErrorResult(
|
|
230
366
|
error = OpenIAPError.PurchaseFailed(),
|
|
@@ -243,10 +379,15 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
243
379
|
val androidOptions = options?.android
|
|
244
380
|
initConnection().await()
|
|
245
381
|
|
|
382
|
+
RnIapLog.payload(
|
|
383
|
+
"getAvailablePurchases",
|
|
384
|
+
mapOf("type" to androidOptions?.type?.name)
|
|
385
|
+
)
|
|
386
|
+
|
|
246
387
|
val typeName = androidOptions?.type?.name?.lowercase()
|
|
247
388
|
val normalizedType = when (typeName) {
|
|
248
389
|
"inapp" -> {
|
|
249
|
-
|
|
390
|
+
RnIapLog.warn("getAvailablePurchases received legacy type 'inapp'; forwarding as 'in-app'")
|
|
250
391
|
"in-app"
|
|
251
392
|
}
|
|
252
393
|
"in-app", "subs" -> typeName
|
|
@@ -254,11 +395,20 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
254
395
|
}
|
|
255
396
|
|
|
256
397
|
val result: List<OpenIapPurchase> = if (normalizedType != null) {
|
|
257
|
-
val typeEnum =
|
|
398
|
+
val typeEnum = parseProductQueryType(normalizedType)
|
|
399
|
+
RnIapLog.payload(
|
|
400
|
+
"getAvailablePurchases.native",
|
|
401
|
+
mapOf("type" to typeEnum.rawValue)
|
|
402
|
+
)
|
|
258
403
|
openIap.getAvailableItems(typeEnum)
|
|
259
404
|
} else {
|
|
260
|
-
|
|
405
|
+
RnIapLog.payload("getAvailablePurchases.native", mapOf("type" to "all"))
|
|
406
|
+
openIap.getAvailablePurchases(null)
|
|
261
407
|
}
|
|
408
|
+
RnIapLog.result(
|
|
409
|
+
"getAvailablePurchases",
|
|
410
|
+
result.map { mapOf("id" to it.id, "sku" to it.productId) }
|
|
411
|
+
)
|
|
262
412
|
result.map { convertToNitroPurchase(it) }.toTypedArray()
|
|
263
413
|
}
|
|
264
414
|
}
|
|
@@ -270,8 +420,17 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
270
420
|
val purchaseToken = androidParams.purchaseToken
|
|
271
421
|
val isConsumable = androidParams.isConsumable ?: false
|
|
272
422
|
|
|
423
|
+
RnIapLog.payload(
|
|
424
|
+
"finishTransaction",
|
|
425
|
+
mapOf(
|
|
426
|
+
"purchaseToken" to purchaseToken?.let { "<hidden>" },
|
|
427
|
+
"isConsumable" to isConsumable
|
|
428
|
+
)
|
|
429
|
+
)
|
|
430
|
+
|
|
273
431
|
// Validate token early to avoid confusing native errors
|
|
274
432
|
if (purchaseToken.isNullOrBlank()) {
|
|
433
|
+
RnIapLog.warn("finishTransaction called with missing purchaseToken")
|
|
275
434
|
return@async Variant_Boolean_NitroPurchaseResult.Second(
|
|
276
435
|
NitroPurchaseResult(
|
|
277
436
|
responseCode = -1.0,
|
|
@@ -305,7 +464,7 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
305
464
|
} else {
|
|
306
465
|
openIap.acknowledgePurchaseAndroid(purchaseToken)
|
|
307
466
|
}
|
|
308
|
-
Variant_Boolean_NitroPurchaseResult.Second(
|
|
467
|
+
val result = Variant_Boolean_NitroPurchaseResult.Second(
|
|
309
468
|
NitroPurchaseResult(
|
|
310
469
|
responseCode = 0.0,
|
|
311
470
|
debugMessage = null,
|
|
@@ -314,8 +473,11 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
314
473
|
purchaseToken = purchaseToken
|
|
315
474
|
)
|
|
316
475
|
)
|
|
476
|
+
RnIapLog.result("finishTransaction", mapOf("success" to true))
|
|
477
|
+
result
|
|
317
478
|
} catch (e: Exception) {
|
|
318
479
|
val err = OpenIAPError.BillingError()
|
|
480
|
+
RnIapLog.failure("finishTransaction", e)
|
|
319
481
|
Variant_Boolean_NitroPurchaseResult.Second(
|
|
320
482
|
NitroPurchaseResult(
|
|
321
483
|
responseCode = -1.0,
|
|
@@ -354,13 +516,14 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
354
516
|
override fun addPromotedProductListenerIOS(listener: (product: NitroProduct) -> Unit) {
|
|
355
517
|
// Promoted products are iOS-only, but we implement the interface for consistency
|
|
356
518
|
promotedProductListenersIOS.add(listener)
|
|
357
|
-
|
|
519
|
+
RnIapLog.warn("addPromotedProductListenerIOS called on Android - promoted products are iOS-only")
|
|
358
520
|
}
|
|
359
|
-
|
|
521
|
+
|
|
360
522
|
override fun removePromotedProductListenerIOS(listener: (product: NitroProduct) -> Unit) {
|
|
361
523
|
// Promoted products are iOS-only, but we implement the interface for consistency
|
|
362
|
-
promotedProductListenersIOS.
|
|
363
|
-
|
|
524
|
+
val removed = promotedProductListenersIOS.remove(listener)
|
|
525
|
+
if (!removed) RnIapLog.warn("removePromotedProductListenerIOS: listener not found")
|
|
526
|
+
RnIapLog.warn("removePromotedProductListenerIOS called on Android - promoted products are iOS-only")
|
|
364
527
|
}
|
|
365
528
|
|
|
366
529
|
// Billing callbacks handled internally by OpenIAP
|
|
@@ -371,6 +534,10 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
371
534
|
* Send purchase update event to listeners
|
|
372
535
|
*/
|
|
373
536
|
private fun sendPurchaseUpdate(purchase: NitroPurchase) {
|
|
537
|
+
RnIapLog.result(
|
|
538
|
+
"sendPurchaseUpdate",
|
|
539
|
+
mapOf("productId" to purchase.productId, "platform" to purchase.platform)
|
|
540
|
+
)
|
|
374
541
|
for (listener in purchaseUpdatedListeners) {
|
|
375
542
|
listener(purchase)
|
|
376
543
|
}
|
|
@@ -380,6 +547,10 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
380
547
|
* Send purchase error event to listeners
|
|
381
548
|
*/
|
|
382
549
|
private fun sendPurchaseError(error: NitroPurchaseResult) {
|
|
550
|
+
RnIapLog.result(
|
|
551
|
+
"sendPurchaseError",
|
|
552
|
+
mapOf("code" to error.code, "message" to error.message)
|
|
553
|
+
)
|
|
383
554
|
for (listener in purchaseErrorListeners) {
|
|
384
555
|
listener(error)
|
|
385
556
|
}
|
|
@@ -403,12 +574,75 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
403
574
|
purchaseToken = null
|
|
404
575
|
)
|
|
405
576
|
}
|
|
406
|
-
|
|
407
|
-
private fun convertToNitroProduct(product: OpenIapProduct): NitroProduct {
|
|
408
|
-
val subOffers = product.subscriptionOfferDetailsAndroid
|
|
409
|
-
val subOffersJson = subOffers?.let { OpenIapSerialization.toJson(it) }
|
|
410
577
|
|
|
411
|
-
|
|
578
|
+
private fun parseProductQueryType(rawType: String): ProductQueryType {
|
|
579
|
+
val normalized = rawType
|
|
580
|
+
.trim()
|
|
581
|
+
.lowercase(Locale.US)
|
|
582
|
+
.replace("_", "")
|
|
583
|
+
.replace("-", "")
|
|
584
|
+
return when (normalized) {
|
|
585
|
+
"subs", "subscription", "subscriptions" -> ProductQueryType.Subs
|
|
586
|
+
"all" -> ProductQueryType.All
|
|
587
|
+
else -> ProductQueryType.InApp
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
private fun FetchProductsResult.productsOrEmpty(): List<ProductCommon> = when (this) {
|
|
592
|
+
is FetchProductsResultProducts -> this.value.orEmpty().filterIsInstance<ProductCommon>()
|
|
593
|
+
is FetchProductsResultSubscriptions -> this.value.orEmpty().filterIsInstance<ProductCommon>()
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
private fun dev.hyo.openiap.RequestPurchaseResult?.purchasesOrEmpty(): List<OpenIapPurchase> = when (this) {
|
|
597
|
+
is RequestPurchaseResultPurchases -> this.value.orEmpty().mapNotNull { it }
|
|
598
|
+
is RequestPurchaseResultPurchase -> this.value?.let(::listOf).orEmpty()
|
|
599
|
+
else -> emptyList()
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
private fun serializeSubscriptionOffers(offers: List<ProductSubscriptionAndroidOfferDetails>): String {
|
|
603
|
+
val array = JSONArray()
|
|
604
|
+
offers.forEach { offer ->
|
|
605
|
+
val offerJson = JSONObject()
|
|
606
|
+
offerJson.put("basePlanId", offer.basePlanId)
|
|
607
|
+
offerJson.put("offerId", offer.offerId)
|
|
608
|
+
offerJson.put("offerTags", JSONArray(offer.offerTags))
|
|
609
|
+
offerJson.put("offerToken", offer.offerToken)
|
|
610
|
+
|
|
611
|
+
val phasesArray = JSONArray()
|
|
612
|
+
offer.pricingPhases.pricingPhaseList.forEach { phase ->
|
|
613
|
+
val phaseJson = JSONObject()
|
|
614
|
+
phaseJson.put("billingCycleCount", phase.billingCycleCount)
|
|
615
|
+
phaseJson.put("billingPeriod", phase.billingPeriod)
|
|
616
|
+
phaseJson.put("formattedPrice", phase.formattedPrice)
|
|
617
|
+
phaseJson.put("priceAmountMicros", phase.priceAmountMicros)
|
|
618
|
+
phaseJson.put("priceCurrencyCode", phase.priceCurrencyCode)
|
|
619
|
+
phaseJson.put("recurrenceMode", phase.recurrenceMode)
|
|
620
|
+
phasesArray.put(phaseJson)
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
val pricingPhasesJson = JSONObject()
|
|
624
|
+
pricingPhasesJson.put("pricingPhaseList", phasesArray)
|
|
625
|
+
offerJson.put("pricingPhases", pricingPhasesJson)
|
|
626
|
+
|
|
627
|
+
array.put(offerJson)
|
|
628
|
+
}
|
|
629
|
+
return array.toString()
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
private fun convertToNitroProduct(product: ProductCommon): NitroProduct {
|
|
633
|
+
val subscriptionOffers = when (product) {
|
|
634
|
+
is ProductSubscriptionAndroid -> product.subscriptionOfferDetailsAndroid.orEmpty()
|
|
635
|
+
is ProductAndroid -> product.subscriptionOfferDetailsAndroid.orEmpty()
|
|
636
|
+
else -> emptyList()
|
|
637
|
+
}
|
|
638
|
+
val oneTimeOffer = when (product) {
|
|
639
|
+
is ProductSubscriptionAndroid -> product.oneTimePurchaseOfferDetailsAndroid
|
|
640
|
+
is ProductAndroid -> product.oneTimePurchaseOfferDetailsAndroid
|
|
641
|
+
else -> null
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
val subscriptionOffersJson = subscriptionOffers.takeIf { it.isNotEmpty() }?.let { serializeSubscriptionOffers(it) }
|
|
645
|
+
|
|
412
646
|
var originalPriceAndroid: String? = null
|
|
413
647
|
var originalPriceAmountMicrosAndroid: Double? = null
|
|
414
648
|
var introductoryPriceValueAndroid: Double? = null
|
|
@@ -417,33 +651,28 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
417
651
|
var subscriptionPeriodAndroid: String? = null
|
|
418
652
|
var freeTrialPeriodAndroid: String? = null
|
|
419
653
|
|
|
420
|
-
if (product.type ==
|
|
421
|
-
|
|
654
|
+
if (product.type == ProductType.InApp) {
|
|
655
|
+
oneTimeOffer?.let { otp ->
|
|
422
656
|
originalPriceAndroid = otp.formattedPrice
|
|
423
|
-
// priceAmountMicros is a string; parse to number if possible
|
|
424
657
|
originalPriceAmountMicrosAndroid = otp.priceAmountMicros.toDoubleOrNull()
|
|
425
658
|
}
|
|
426
659
|
} else {
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
if (!phases.isNullOrEmpty()) {
|
|
430
|
-
// Base recurring phase: recurrenceMode == 2 (INFINITE), else last non-zero priced phase
|
|
660
|
+
val phases = subscriptionOffers.firstOrNull()?.pricingPhases?.pricingPhaseList.orEmpty()
|
|
661
|
+
if (phases.isNotEmpty()) {
|
|
431
662
|
val basePhase = phases.firstOrNull { it.recurrenceMode == 2 } ?: phases.last()
|
|
432
663
|
originalPriceAndroid = basePhase.formattedPrice
|
|
433
664
|
originalPriceAmountMicrosAndroid = basePhase.priceAmountMicros.toDoubleOrNull()
|
|
434
665
|
subscriptionPeriodAndroid = basePhase.billingPeriod
|
|
435
666
|
|
|
436
|
-
// Introductory phase: finite cycles (>0) and priced (>0)
|
|
437
667
|
val introPhase = phases.firstOrNull {
|
|
438
668
|
it.billingCycleCount > 0 && (it.priceAmountMicros.toLongOrNull() ?: 0L) > 0L
|
|
439
669
|
}
|
|
440
670
|
if (introPhase != null) {
|
|
441
|
-
introductoryPriceValueAndroid =
|
|
671
|
+
introductoryPriceValueAndroid = introPhase.priceAmountMicros.toDoubleOrNull()?.div(1_000_000.0)
|
|
442
672
|
introductoryPriceCyclesAndroid = introPhase.billingCycleCount.toDouble()
|
|
443
673
|
introductoryPricePeriodAndroid = introPhase.billingPeriod
|
|
444
674
|
}
|
|
445
675
|
|
|
446
|
-
// Free trial: zero-priced phase
|
|
447
676
|
val trialPhase = phases.firstOrNull { (it.priceAmountMicros.toLongOrNull() ?: 0L) == 0L }
|
|
448
677
|
if (trialPhase != null) {
|
|
449
678
|
freeTrialPeriodAndroid = trialPhase.billingPeriod
|
|
@@ -455,13 +684,12 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
455
684
|
id = product.id,
|
|
456
685
|
title = product.title,
|
|
457
686
|
description = product.description,
|
|
458
|
-
type = product.type.
|
|
687
|
+
type = product.type.rawValue,
|
|
459
688
|
displayName = product.displayName,
|
|
460
689
|
displayPrice = product.displayPrice,
|
|
461
690
|
currency = product.currency,
|
|
462
691
|
price = product.price,
|
|
463
692
|
platform = IapPlatform.ANDROID,
|
|
464
|
-
// iOS fields (null on Android)
|
|
465
693
|
typeIOS = null,
|
|
466
694
|
isFamilyShareableIOS = null,
|
|
467
695
|
jsonRepresentationIOS = null,
|
|
@@ -472,7 +700,6 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
472
700
|
introductoryPricePaymentModeIOS = null,
|
|
473
701
|
introductoryPriceNumberOfPeriodsIOS = null,
|
|
474
702
|
introductoryPriceSubscriptionPeriodIOS = null,
|
|
475
|
-
// Android derivations
|
|
476
703
|
originalPriceAndroid = originalPriceAndroid,
|
|
477
704
|
originalPriceAmountMicrosAndroid = originalPriceAmountMicrosAndroid,
|
|
478
705
|
introductoryPriceValueAndroid = introductoryPriceValueAndroid,
|
|
@@ -480,55 +707,52 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
480
707
|
introductoryPricePeriodAndroid = introductoryPricePeriodAndroid,
|
|
481
708
|
subscriptionPeriodAndroid = subscriptionPeriodAndroid,
|
|
482
709
|
freeTrialPeriodAndroid = freeTrialPeriodAndroid,
|
|
483
|
-
subscriptionOfferDetailsAndroid =
|
|
710
|
+
subscriptionOfferDetailsAndroid = subscriptionOffersJson
|
|
484
711
|
)
|
|
485
712
|
}
|
|
486
713
|
|
|
487
714
|
// Purchase state is provided as enum value by OpenIAP
|
|
488
715
|
|
|
489
716
|
private fun convertToNitroPurchase(purchase: OpenIapPurchase): NitroPurchase {
|
|
490
|
-
|
|
717
|
+
val androidPurchase = purchase as? PurchaseAndroid
|
|
491
718
|
val purchaseStateAndroidNumeric = when (purchase.purchaseState) {
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
else -> 0.0
|
|
719
|
+
dev.hyo.openiap.PurchaseState.Purchased -> 1.0
|
|
720
|
+
dev.hyo.openiap.PurchaseState.Pending -> 2.0
|
|
721
|
+
else -> 0.0
|
|
495
722
|
}
|
|
496
723
|
return NitroPurchase(
|
|
497
724
|
id = purchase.id,
|
|
498
725
|
productId = purchase.productId,
|
|
499
|
-
transactionDate = purchase.transactionDate
|
|
726
|
+
transactionDate = purchase.transactionDate,
|
|
500
727
|
purchaseToken = purchase.purchaseToken,
|
|
501
728
|
platform = IapPlatform.ANDROID,
|
|
502
|
-
// Common fields
|
|
503
729
|
quantity = purchase.quantity.toDouble(),
|
|
504
730
|
purchaseState = mapPurchaseState(purchase.purchaseState),
|
|
505
731
|
isAutoRenewing = purchase.isAutoRenewing,
|
|
506
|
-
// iOS fields
|
|
507
732
|
quantityIOS = null,
|
|
508
733
|
originalTransactionDateIOS = null,
|
|
509
734
|
originalTransactionIdentifierIOS = null,
|
|
510
735
|
appAccountToken = null,
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
autoRenewingAndroid = purchase.autoRenewingAndroid,
|
|
736
|
+
purchaseTokenAndroid = androidPurchase?.purchaseToken,
|
|
737
|
+
dataAndroid = androidPurchase?.dataAndroid,
|
|
738
|
+
signatureAndroid = androidPurchase?.signatureAndroid,
|
|
739
|
+
autoRenewingAndroid = androidPurchase?.autoRenewingAndroid,
|
|
516
740
|
purchaseStateAndroid = purchaseStateAndroidNumeric,
|
|
517
|
-
isAcknowledgedAndroid =
|
|
518
|
-
packageNameAndroid =
|
|
519
|
-
obfuscatedAccountIdAndroid =
|
|
520
|
-
obfuscatedProfileIdAndroid =
|
|
741
|
+
isAcknowledgedAndroid = androidPurchase?.isAcknowledgedAndroid,
|
|
742
|
+
packageNameAndroid = androidPurchase?.packageNameAndroid,
|
|
743
|
+
obfuscatedAccountIdAndroid = androidPurchase?.obfuscatedAccountIdAndroid,
|
|
744
|
+
obfuscatedProfileIdAndroid = androidPurchase?.obfuscatedProfileIdAndroid
|
|
521
745
|
)
|
|
522
746
|
}
|
|
523
747
|
|
|
524
|
-
private fun mapPurchaseState(state:
|
|
525
|
-
return when (state
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
748
|
+
private fun mapPurchaseState(state: dev.hyo.openiap.PurchaseState): PurchaseState {
|
|
749
|
+
return when (state) {
|
|
750
|
+
dev.hyo.openiap.PurchaseState.Purchased -> PurchaseState.PURCHASED
|
|
751
|
+
dev.hyo.openiap.PurchaseState.Pending -> PurchaseState.PENDING
|
|
752
|
+
dev.hyo.openiap.PurchaseState.Deferred -> PurchaseState.DEFERRED
|
|
753
|
+
dev.hyo.openiap.PurchaseState.Restored -> PurchaseState.RESTORED
|
|
754
|
+
dev.hyo.openiap.PurchaseState.Failed -> PurchaseState.FAILED
|
|
755
|
+
dev.hyo.openiap.PurchaseState.Unknown -> PurchaseState.UNKNOWN
|
|
532
756
|
}
|
|
533
757
|
}
|
|
534
758
|
|
|
@@ -553,9 +777,12 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
553
777
|
return Promise.async {
|
|
554
778
|
try {
|
|
555
779
|
initConnection().await()
|
|
556
|
-
|
|
780
|
+
RnIapLog.payload("getStorefrontAndroid", null)
|
|
781
|
+
val value = openIap.getStorefront()
|
|
782
|
+
RnIapLog.result("getStorefrontAndroid", value)
|
|
783
|
+
value
|
|
557
784
|
} catch (e: Exception) {
|
|
558
|
-
|
|
785
|
+
RnIapLog.failure("getStorefrontAndroid", e)
|
|
559
786
|
""
|
|
560
787
|
}
|
|
561
788
|
}
|
|
@@ -570,13 +797,21 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
570
797
|
skuAndroid = options.skuAndroid,
|
|
571
798
|
packageNameAndroid = options.packageNameAndroid
|
|
572
799
|
).let { openIap.deepLinkToSubscriptions(it) }
|
|
800
|
+
RnIapLog.result("deepLinkToSubscriptionsAndroid", true)
|
|
573
801
|
} catch (e: Exception) {
|
|
574
|
-
|
|
802
|
+
RnIapLog.failure("deepLinkToSubscriptionsAndroid", e)
|
|
575
803
|
throw e
|
|
576
804
|
}
|
|
577
805
|
}
|
|
578
806
|
}
|
|
579
807
|
|
|
808
|
+
// iOS-specific method - not supported on Android
|
|
809
|
+
override fun getPromotedProductIOS(): Promise<NitroProduct?> {
|
|
810
|
+
return Promise.async {
|
|
811
|
+
null
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
|
|
580
815
|
// iOS-specific method - not supported on Android
|
|
581
816
|
override fun requestPromotedProductIOS(): Promise<NitroProduct?> {
|
|
582
817
|
return Promise.async {
|
|
@@ -624,6 +859,12 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
624
859
|
}
|
|
625
860
|
}
|
|
626
861
|
|
|
862
|
+
override fun deepLinkToSubscriptionsIOS(): Promise<Boolean> {
|
|
863
|
+
return Promise.async {
|
|
864
|
+
false
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
|
|
627
868
|
// Receipt validation
|
|
628
869
|
override fun validateReceipt(params: NitroReceiptValidationParams): Promise<Variant_NitroReceiptValidationResultIOS_NitroReceiptValidationResultAndroid> {
|
|
629
870
|
return Promise.async {
|
|
@@ -724,7 +965,19 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
724
965
|
throw Exception(toErrorJson(OpenIAPError.NotSupported))
|
|
725
966
|
}
|
|
726
967
|
}
|
|
727
|
-
|
|
968
|
+
|
|
969
|
+
override fun getReceiptIOS(): Promise<String> {
|
|
970
|
+
return Promise.async {
|
|
971
|
+
throw Exception(toErrorJson(OpenIAPError.NotSupported))
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
override fun requestReceiptRefreshIOS(): Promise<String> {
|
|
976
|
+
return Promise.async {
|
|
977
|
+
throw Exception(toErrorJson(OpenIAPError.NotSupported))
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
|
|
728
981
|
override fun isTransactionVerifiedIOS(sku: String): Promise<Boolean> {
|
|
729
982
|
return Promise.async {
|
|
730
983
|
throw Exception(toErrorJson(OpenIAPError.NotSupported))
|
|
@@ -746,9 +999,10 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
746
999
|
debugMessage: String? = null,
|
|
747
1000
|
messageOverride: String? = null
|
|
748
1001
|
): String {
|
|
749
|
-
val code = OpenIAPError.toCode(error)
|
|
1002
|
+
val code = OpenIAPError.Companion.toCode(error)
|
|
750
1003
|
val message = messageOverride?.takeIf { it.isNotBlank() }
|
|
751
|
-
?: error.message
|
|
1004
|
+
?: error.message?.takeIf { it.isNotBlank() }
|
|
1005
|
+
?: OpenIAPError.Companion.defaultMessage(code)
|
|
752
1006
|
return BillingUtils.createErrorJson(
|
|
753
1007
|
code = code,
|
|
754
1008
|
message = message,
|
|
@@ -764,9 +1018,10 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
764
1018
|
debugMessage: String? = null,
|
|
765
1019
|
messageOverride: String? = null
|
|
766
1020
|
): NitroPurchaseResult {
|
|
767
|
-
val code = OpenIAPError.toCode(error)
|
|
1021
|
+
val code = OpenIAPError.Companion.toCode(error)
|
|
768
1022
|
val message = messageOverride?.takeIf { it.isNotBlank() }
|
|
769
|
-
?: error.message
|
|
1023
|
+
?: error.message?.takeIf { it.isNotBlank() }
|
|
1024
|
+
?: OpenIAPError.Companion.defaultMessage(code)
|
|
770
1025
|
return NitroPurchaseResult(
|
|
771
1026
|
responseCode = -1.0,
|
|
772
1027
|
debugMessage = debugMessage ?: error.message,
|