react-native-iap 14.3.9 → 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 +369 -124
- 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,28 +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.OpenIapRequestPurchaseProps
|
|
13
|
-
import dev.hyo.openiap.models.OpenIapSerialization
|
|
14
|
-
import dev.hyo.openiap.models.OpenIapProduct
|
|
15
|
-
import dev.hyo.openiap.models.OpenIapPurchase
|
|
16
|
-
import dev.hyo.openiap.models.ProductRequest
|
|
17
|
-
import dev.hyo.openiap.models.RequestSubscriptionAndroidProps.SubscriptionOffer as OpenIapSubscriptionOffer
|
|
18
31
|
import kotlinx.coroutines.Dispatchers
|
|
19
32
|
import kotlinx.coroutines.withContext
|
|
20
33
|
import kotlinx.coroutines.CompletableDeferred
|
|
34
|
+
import org.json.JSONArray
|
|
35
|
+
import org.json.JSONObject
|
|
36
|
+
import java.util.Locale
|
|
21
37
|
|
|
22
38
|
class HybridRnIap : HybridRnIapSpec() {
|
|
23
|
-
companion object {
|
|
24
|
-
const val TAG = "RnIap"
|
|
25
|
-
}
|
|
26
39
|
|
|
27
40
|
// Get ReactApplicationContext lazily from NitroModules
|
|
28
41
|
private val context: ReactApplicationContext by lazy {
|
|
@@ -45,8 +58,12 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
45
58
|
// Connection methods
|
|
46
59
|
override fun initConnection(): Promise<Boolean> {
|
|
47
60
|
return Promise.async {
|
|
61
|
+
RnIapLog.payload("initConnection", null)
|
|
48
62
|
// Fast-path: if already initialized, return immediately
|
|
49
|
-
if (isInitialized)
|
|
63
|
+
if (isInitialized) {
|
|
64
|
+
RnIapLog.result("initConnection", true)
|
|
65
|
+
return@async true
|
|
66
|
+
}
|
|
50
67
|
|
|
51
68
|
// Set current activity best-effort; don't fail init if missing
|
|
52
69
|
withContext(Dispatchers.Main) {
|
|
@@ -60,18 +77,32 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
60
77
|
false
|
|
61
78
|
} else true
|
|
62
79
|
}
|
|
63
|
-
if (wasExisting)
|
|
80
|
+
if (wasExisting) {
|
|
81
|
+
val result = initDeferred!!.await()
|
|
82
|
+
RnIapLog.result("initConnection.await", result)
|
|
83
|
+
return@async result
|
|
84
|
+
}
|
|
64
85
|
|
|
65
86
|
if (!listenersAttached) {
|
|
66
87
|
listenersAttached = true
|
|
88
|
+
RnIapLog.payload("listeners.attach", null)
|
|
67
89
|
openIap.addPurchaseUpdateListener(OpenIapPurchaseUpdateListener { p ->
|
|
68
|
-
runCatching {
|
|
69
|
-
.
|
|
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) }
|
|
70
97
|
})
|
|
71
98
|
openIap.addPurchaseErrorListener(OpenIapPurchaseErrorListener { e ->
|
|
72
99
|
val code = OpenIAPError.toCode(e)
|
|
73
100
|
val message = e.message ?: OpenIAPError.defaultMessage(code)
|
|
74
101
|
runCatching {
|
|
102
|
+
RnIapLog.result(
|
|
103
|
+
"purchaseErrorListener",
|
|
104
|
+
mapOf("code" to code, "message" to message)
|
|
105
|
+
)
|
|
75
106
|
sendPurchaseError(
|
|
76
107
|
NitroPurchaseResult(
|
|
77
108
|
responseCode = -1.0,
|
|
@@ -81,15 +112,22 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
81
112
|
purchaseToken = null
|
|
82
113
|
)
|
|
83
114
|
)
|
|
84
|
-
}.onFailure {
|
|
115
|
+
}.onFailure { RnIapLog.failure("purchaseErrorListener", it) }
|
|
85
116
|
})
|
|
117
|
+
RnIapLog.result("listeners.attach", "attached")
|
|
86
118
|
}
|
|
87
119
|
|
|
88
120
|
// We created it above; reuse the shared instance
|
|
89
121
|
val deferred = initDeferred!!
|
|
90
122
|
try {
|
|
91
|
-
val ok =
|
|
123
|
+
val ok = try {
|
|
124
|
+
RnIapLog.payload("initConnection.native", null)
|
|
125
|
+
withContext(Dispatchers.Main) {
|
|
126
|
+
openIap.initConnection()
|
|
127
|
+
}
|
|
128
|
+
} catch (err: Throwable) {
|
|
92
129
|
val error = OpenIAPError.InitConnection()
|
|
130
|
+
RnIapLog.failure("initConnection.native", err)
|
|
93
131
|
throw Exception(
|
|
94
132
|
toErrorJson(
|
|
95
133
|
error = error,
|
|
@@ -100,6 +138,7 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
100
138
|
}
|
|
101
139
|
if (!ok) {
|
|
102
140
|
val error = OpenIAPError.InitConnection()
|
|
141
|
+
RnIapLog.failure("initConnection.native", Exception(error.message))
|
|
103
142
|
throw Exception(
|
|
104
143
|
toErrorJson(
|
|
105
144
|
error = error,
|
|
@@ -109,11 +148,13 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
109
148
|
}
|
|
110
149
|
isInitialized = true
|
|
111
150
|
deferred.complete(true)
|
|
151
|
+
RnIapLog.result("initConnection", true)
|
|
112
152
|
true
|
|
113
153
|
} catch (e: Exception) {
|
|
114
154
|
// Complete exceptionally so all concurrent awaiters receive the same failure
|
|
115
155
|
if (!deferred.isCompleted) deferred.completeExceptionally(e)
|
|
116
156
|
isInitialized = false
|
|
157
|
+
RnIapLog.failure("initConnection", e)
|
|
117
158
|
throw e
|
|
118
159
|
} finally {
|
|
119
160
|
initDeferred = null
|
|
@@ -123,10 +164,12 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
123
164
|
|
|
124
165
|
override fun endConnection(): Promise<Boolean> {
|
|
125
166
|
return Promise.async {
|
|
167
|
+
RnIapLog.payload("endConnection", null)
|
|
126
168
|
runCatching { openIap.endConnection() }
|
|
127
169
|
productTypeBySku.clear()
|
|
128
170
|
isInitialized = false
|
|
129
171
|
initDeferred = null
|
|
172
|
+
RnIapLog.result("endConnection", true)
|
|
130
173
|
true
|
|
131
174
|
}
|
|
132
175
|
}
|
|
@@ -134,7 +177,13 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
134
177
|
// Product methods
|
|
135
178
|
override fun fetchProducts(skus: Array<String>, type: String): Promise<Array<NitroProduct>> {
|
|
136
179
|
return Promise.async {
|
|
137
|
-
|
|
180
|
+
RnIapLog.payload(
|
|
181
|
+
"fetchProducts",
|
|
182
|
+
mapOf(
|
|
183
|
+
"skus" to skus.toList(),
|
|
184
|
+
"type" to type
|
|
185
|
+
)
|
|
186
|
+
)
|
|
138
187
|
|
|
139
188
|
if (skus.isEmpty()) {
|
|
140
189
|
throw Exception(toErrorJson(OpenIAPError.EmptySkuList))
|
|
@@ -142,40 +191,46 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
142
191
|
|
|
143
192
|
initConnection().await()
|
|
144
193
|
|
|
145
|
-
val
|
|
194
|
+
val queryType = parseProductQueryType(type)
|
|
146
195
|
val skusList = skus.toList()
|
|
147
196
|
|
|
148
|
-
val products: List<
|
|
149
|
-
|
|
150
|
-
val collected =
|
|
151
|
-
listOf(
|
|
152
|
-
|
|
153
|
-
|
|
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
|
+
)
|
|
154
210
|
fetched.forEach { collected[it.id] = it }
|
|
155
211
|
}
|
|
156
212
|
collected.values.toList()
|
|
157
213
|
}
|
|
158
|
-
"inapp", "in-app" -> {
|
|
159
|
-
if (normalizedType == "inapp") {
|
|
160
|
-
Log.w(TAG, "fetchProducts received legacy type 'inapp'; forwarding as 'in-app'")
|
|
161
|
-
}
|
|
162
|
-
val requestType = ProductRequest.ProductRequestType.fromString("in-app")
|
|
163
|
-
openIap.fetchProducts(ProductRequest(skusList, requestType))
|
|
164
|
-
}
|
|
165
|
-
"subs" -> {
|
|
166
|
-
val requestType = ProductRequest.ProductRequestType.fromString("subs")
|
|
167
|
-
openIap.fetchProducts(ProductRequest(skusList, requestType))
|
|
168
|
-
}
|
|
169
214
|
else -> {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
|
173
225
|
}
|
|
174
226
|
}
|
|
175
227
|
|
|
176
|
-
|
|
177
|
-
products.forEach { p -> productTypeBySku[p.id] = p.type.value }
|
|
228
|
+
products.forEach { p -> productTypeBySku[p.id] = p.type.rawValue }
|
|
178
229
|
|
|
230
|
+
RnIapLog.result(
|
|
231
|
+
"fetchProducts",
|
|
232
|
+
products.map { mapOf("id" to it.id, "type" to it.type.rawValue) }
|
|
233
|
+
)
|
|
179
234
|
products.map { convertToNitroProduct(it) }.toTypedArray()
|
|
180
235
|
}
|
|
181
236
|
}
|
|
@@ -184,15 +239,24 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
184
239
|
// Purchase methods (Unified)
|
|
185
240
|
override fun requestPurchase(request: NitroPurchaseRequest): Promise<RequestPurchaseResult?> {
|
|
186
241
|
return Promise.async {
|
|
187
|
-
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
|
+
)
|
|
188
251
|
|
|
189
252
|
val androidRequest = request.android ?: run {
|
|
190
|
-
|
|
253
|
+
RnIapLog.warn("requestPurchase called without android payload")
|
|
191
254
|
sendPurchaseError(toErrorResult(OpenIAPError.DeveloperError))
|
|
192
255
|
return@async defaultResult
|
|
193
256
|
}
|
|
194
257
|
|
|
195
258
|
if (androidRequest.skus.isEmpty()) {
|
|
259
|
+
RnIapLog.warn("requestPurchase received empty SKU list")
|
|
196
260
|
sendPurchaseError(toErrorResult(OpenIAPError.EmptySkuList))
|
|
197
261
|
return@async defaultResult
|
|
198
262
|
}
|
|
@@ -201,40 +265,102 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
201
265
|
initConnection().await()
|
|
202
266
|
withContext(Dispatchers.Main) { runCatching { openIap.setActivity(context.currentActivity) } }
|
|
203
267
|
|
|
204
|
-
val
|
|
205
|
-
if (
|
|
206
|
-
|
|
207
|
-
|
|
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
|
+
}
|
|
208
286
|
}
|
|
209
|
-
|
|
210
|
-
val
|
|
287
|
+
|
|
288
|
+
val typeHint = androidRequest.skus.firstOrNull()?.let { productTypeBySku[it] } ?: "inapp"
|
|
289
|
+
val queryType = parseProductQueryType(typeHint)
|
|
211
290
|
|
|
212
291
|
val subscriptionOffers = androidRequest.subscriptionOffers
|
|
213
|
-
?.
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
|
217
321
|
)
|
|
218
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
|
+
}
|
|
219
338
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
),
|
|
228
|
-
typeEnum
|
|
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
|
+
)
|
|
229
346
|
)
|
|
230
347
|
|
|
231
|
-
result.
|
|
232
|
-
|
|
233
|
-
|
|
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) }
|
|
234
359
|
}
|
|
235
360
|
|
|
236
361
|
defaultResult
|
|
237
362
|
} catch (e: Exception) {
|
|
363
|
+
RnIapLog.failure("requestPurchase", e)
|
|
238
364
|
sendPurchaseError(
|
|
239
365
|
toErrorResult(
|
|
240
366
|
error = OpenIAPError.PurchaseFailed(),
|
|
@@ -253,10 +379,15 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
253
379
|
val androidOptions = options?.android
|
|
254
380
|
initConnection().await()
|
|
255
381
|
|
|
382
|
+
RnIapLog.payload(
|
|
383
|
+
"getAvailablePurchases",
|
|
384
|
+
mapOf("type" to androidOptions?.type?.name)
|
|
385
|
+
)
|
|
386
|
+
|
|
256
387
|
val typeName = androidOptions?.type?.name?.lowercase()
|
|
257
388
|
val normalizedType = when (typeName) {
|
|
258
389
|
"inapp" -> {
|
|
259
|
-
|
|
390
|
+
RnIapLog.warn("getAvailablePurchases received legacy type 'inapp'; forwarding as 'in-app'")
|
|
260
391
|
"in-app"
|
|
261
392
|
}
|
|
262
393
|
"in-app", "subs" -> typeName
|
|
@@ -264,11 +395,20 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
264
395
|
}
|
|
265
396
|
|
|
266
397
|
val result: List<OpenIapPurchase> = if (normalizedType != null) {
|
|
267
|
-
val typeEnum =
|
|
398
|
+
val typeEnum = parseProductQueryType(normalizedType)
|
|
399
|
+
RnIapLog.payload(
|
|
400
|
+
"getAvailablePurchases.native",
|
|
401
|
+
mapOf("type" to typeEnum.rawValue)
|
|
402
|
+
)
|
|
268
403
|
openIap.getAvailableItems(typeEnum)
|
|
269
404
|
} else {
|
|
270
|
-
|
|
405
|
+
RnIapLog.payload("getAvailablePurchases.native", mapOf("type" to "all"))
|
|
406
|
+
openIap.getAvailablePurchases(null)
|
|
271
407
|
}
|
|
408
|
+
RnIapLog.result(
|
|
409
|
+
"getAvailablePurchases",
|
|
410
|
+
result.map { mapOf("id" to it.id, "sku" to it.productId) }
|
|
411
|
+
)
|
|
272
412
|
result.map { convertToNitroPurchase(it) }.toTypedArray()
|
|
273
413
|
}
|
|
274
414
|
}
|
|
@@ -280,8 +420,17 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
280
420
|
val purchaseToken = androidParams.purchaseToken
|
|
281
421
|
val isConsumable = androidParams.isConsumable ?: false
|
|
282
422
|
|
|
423
|
+
RnIapLog.payload(
|
|
424
|
+
"finishTransaction",
|
|
425
|
+
mapOf(
|
|
426
|
+
"purchaseToken" to purchaseToken?.let { "<hidden>" },
|
|
427
|
+
"isConsumable" to isConsumable
|
|
428
|
+
)
|
|
429
|
+
)
|
|
430
|
+
|
|
283
431
|
// Validate token early to avoid confusing native errors
|
|
284
432
|
if (purchaseToken.isNullOrBlank()) {
|
|
433
|
+
RnIapLog.warn("finishTransaction called with missing purchaseToken")
|
|
285
434
|
return@async Variant_Boolean_NitroPurchaseResult.Second(
|
|
286
435
|
NitroPurchaseResult(
|
|
287
436
|
responseCode = -1.0,
|
|
@@ -315,7 +464,7 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
315
464
|
} else {
|
|
316
465
|
openIap.acknowledgePurchaseAndroid(purchaseToken)
|
|
317
466
|
}
|
|
318
|
-
Variant_Boolean_NitroPurchaseResult.Second(
|
|
467
|
+
val result = Variant_Boolean_NitroPurchaseResult.Second(
|
|
319
468
|
NitroPurchaseResult(
|
|
320
469
|
responseCode = 0.0,
|
|
321
470
|
debugMessage = null,
|
|
@@ -324,8 +473,11 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
324
473
|
purchaseToken = purchaseToken
|
|
325
474
|
)
|
|
326
475
|
)
|
|
476
|
+
RnIapLog.result("finishTransaction", mapOf("success" to true))
|
|
477
|
+
result
|
|
327
478
|
} catch (e: Exception) {
|
|
328
479
|
val err = OpenIAPError.BillingError()
|
|
480
|
+
RnIapLog.failure("finishTransaction", e)
|
|
329
481
|
Variant_Boolean_NitroPurchaseResult.Second(
|
|
330
482
|
NitroPurchaseResult(
|
|
331
483
|
responseCode = -1.0,
|
|
@@ -364,13 +516,14 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
364
516
|
override fun addPromotedProductListenerIOS(listener: (product: NitroProduct) -> Unit) {
|
|
365
517
|
// Promoted products are iOS-only, but we implement the interface for consistency
|
|
366
518
|
promotedProductListenersIOS.add(listener)
|
|
367
|
-
|
|
519
|
+
RnIapLog.warn("addPromotedProductListenerIOS called on Android - promoted products are iOS-only")
|
|
368
520
|
}
|
|
369
|
-
|
|
521
|
+
|
|
370
522
|
override fun removePromotedProductListenerIOS(listener: (product: NitroProduct) -> Unit) {
|
|
371
523
|
// Promoted products are iOS-only, but we implement the interface for consistency
|
|
372
|
-
promotedProductListenersIOS.
|
|
373
|
-
|
|
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")
|
|
374
527
|
}
|
|
375
528
|
|
|
376
529
|
// Billing callbacks handled internally by OpenIAP
|
|
@@ -381,6 +534,10 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
381
534
|
* Send purchase update event to listeners
|
|
382
535
|
*/
|
|
383
536
|
private fun sendPurchaseUpdate(purchase: NitroPurchase) {
|
|
537
|
+
RnIapLog.result(
|
|
538
|
+
"sendPurchaseUpdate",
|
|
539
|
+
mapOf("productId" to purchase.productId, "platform" to purchase.platform)
|
|
540
|
+
)
|
|
384
541
|
for (listener in purchaseUpdatedListeners) {
|
|
385
542
|
listener(purchase)
|
|
386
543
|
}
|
|
@@ -390,6 +547,10 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
390
547
|
* Send purchase error event to listeners
|
|
391
548
|
*/
|
|
392
549
|
private fun sendPurchaseError(error: NitroPurchaseResult) {
|
|
550
|
+
RnIapLog.result(
|
|
551
|
+
"sendPurchaseError",
|
|
552
|
+
mapOf("code" to error.code, "message" to error.message)
|
|
553
|
+
)
|
|
393
554
|
for (listener in purchaseErrorListeners) {
|
|
394
555
|
listener(error)
|
|
395
556
|
}
|
|
@@ -413,12 +574,75 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
413
574
|
purchaseToken = null
|
|
414
575
|
)
|
|
415
576
|
}
|
|
416
|
-
|
|
417
|
-
private fun convertToNitroProduct(product: OpenIapProduct): NitroProduct {
|
|
418
|
-
val subOffers = product.subscriptionOfferDetailsAndroid
|
|
419
|
-
val subOffersJson = subOffers?.let { OpenIapSerialization.toJson(it) }
|
|
420
577
|
|
|
421
|
-
|
|
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
|
+
|
|
422
646
|
var originalPriceAndroid: String? = null
|
|
423
647
|
var originalPriceAmountMicrosAndroid: Double? = null
|
|
424
648
|
var introductoryPriceValueAndroid: Double? = null
|
|
@@ -427,33 +651,28 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
427
651
|
var subscriptionPeriodAndroid: String? = null
|
|
428
652
|
var freeTrialPeriodAndroid: String? = null
|
|
429
653
|
|
|
430
|
-
if (product.type ==
|
|
431
|
-
|
|
654
|
+
if (product.type == ProductType.InApp) {
|
|
655
|
+
oneTimeOffer?.let { otp ->
|
|
432
656
|
originalPriceAndroid = otp.formattedPrice
|
|
433
|
-
// priceAmountMicros is a string; parse to number if possible
|
|
434
657
|
originalPriceAmountMicrosAndroid = otp.priceAmountMicros.toDoubleOrNull()
|
|
435
658
|
}
|
|
436
659
|
} else {
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
if (!phases.isNullOrEmpty()) {
|
|
440
|
-
// Base recurring phase: recurrenceMode == 2 (INFINITE), else last non-zero priced phase
|
|
660
|
+
val phases = subscriptionOffers.firstOrNull()?.pricingPhases?.pricingPhaseList.orEmpty()
|
|
661
|
+
if (phases.isNotEmpty()) {
|
|
441
662
|
val basePhase = phases.firstOrNull { it.recurrenceMode == 2 } ?: phases.last()
|
|
442
663
|
originalPriceAndroid = basePhase.formattedPrice
|
|
443
664
|
originalPriceAmountMicrosAndroid = basePhase.priceAmountMicros.toDoubleOrNull()
|
|
444
665
|
subscriptionPeriodAndroid = basePhase.billingPeriod
|
|
445
666
|
|
|
446
|
-
// Introductory phase: finite cycles (>0) and priced (>0)
|
|
447
667
|
val introPhase = phases.firstOrNull {
|
|
448
668
|
it.billingCycleCount > 0 && (it.priceAmountMicros.toLongOrNull() ?: 0L) > 0L
|
|
449
669
|
}
|
|
450
670
|
if (introPhase != null) {
|
|
451
|
-
introductoryPriceValueAndroid =
|
|
671
|
+
introductoryPriceValueAndroid = introPhase.priceAmountMicros.toDoubleOrNull()?.div(1_000_000.0)
|
|
452
672
|
introductoryPriceCyclesAndroid = introPhase.billingCycleCount.toDouble()
|
|
453
673
|
introductoryPricePeriodAndroid = introPhase.billingPeriod
|
|
454
674
|
}
|
|
455
675
|
|
|
456
|
-
// Free trial: zero-priced phase
|
|
457
676
|
val trialPhase = phases.firstOrNull { (it.priceAmountMicros.toLongOrNull() ?: 0L) == 0L }
|
|
458
677
|
if (trialPhase != null) {
|
|
459
678
|
freeTrialPeriodAndroid = trialPhase.billingPeriod
|
|
@@ -465,13 +684,12 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
465
684
|
id = product.id,
|
|
466
685
|
title = product.title,
|
|
467
686
|
description = product.description,
|
|
468
|
-
type = product.type.
|
|
687
|
+
type = product.type.rawValue,
|
|
469
688
|
displayName = product.displayName,
|
|
470
689
|
displayPrice = product.displayPrice,
|
|
471
690
|
currency = product.currency,
|
|
472
691
|
price = product.price,
|
|
473
692
|
platform = IapPlatform.ANDROID,
|
|
474
|
-
// iOS fields (null on Android)
|
|
475
693
|
typeIOS = null,
|
|
476
694
|
isFamilyShareableIOS = null,
|
|
477
695
|
jsonRepresentationIOS = null,
|
|
@@ -482,7 +700,6 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
482
700
|
introductoryPricePaymentModeIOS = null,
|
|
483
701
|
introductoryPriceNumberOfPeriodsIOS = null,
|
|
484
702
|
introductoryPriceSubscriptionPeriodIOS = null,
|
|
485
|
-
// Android derivations
|
|
486
703
|
originalPriceAndroid = originalPriceAndroid,
|
|
487
704
|
originalPriceAmountMicrosAndroid = originalPriceAmountMicrosAndroid,
|
|
488
705
|
introductoryPriceValueAndroid = introductoryPriceValueAndroid,
|
|
@@ -490,55 +707,52 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
490
707
|
introductoryPricePeriodAndroid = introductoryPricePeriodAndroid,
|
|
491
708
|
subscriptionPeriodAndroid = subscriptionPeriodAndroid,
|
|
492
709
|
freeTrialPeriodAndroid = freeTrialPeriodAndroid,
|
|
493
|
-
subscriptionOfferDetailsAndroid =
|
|
710
|
+
subscriptionOfferDetailsAndroid = subscriptionOffersJson
|
|
494
711
|
)
|
|
495
712
|
}
|
|
496
713
|
|
|
497
714
|
// Purchase state is provided as enum value by OpenIAP
|
|
498
715
|
|
|
499
716
|
private fun convertToNitroPurchase(purchase: OpenIapPurchase): NitroPurchase {
|
|
500
|
-
|
|
717
|
+
val androidPurchase = purchase as? PurchaseAndroid
|
|
501
718
|
val purchaseStateAndroidNumeric = when (purchase.purchaseState) {
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
else -> 0.0
|
|
719
|
+
dev.hyo.openiap.PurchaseState.Purchased -> 1.0
|
|
720
|
+
dev.hyo.openiap.PurchaseState.Pending -> 2.0
|
|
721
|
+
else -> 0.0
|
|
505
722
|
}
|
|
506
723
|
return NitroPurchase(
|
|
507
724
|
id = purchase.id,
|
|
508
725
|
productId = purchase.productId,
|
|
509
|
-
transactionDate = purchase.transactionDate
|
|
726
|
+
transactionDate = purchase.transactionDate,
|
|
510
727
|
purchaseToken = purchase.purchaseToken,
|
|
511
728
|
platform = IapPlatform.ANDROID,
|
|
512
|
-
// Common fields
|
|
513
729
|
quantity = purchase.quantity.toDouble(),
|
|
514
730
|
purchaseState = mapPurchaseState(purchase.purchaseState),
|
|
515
731
|
isAutoRenewing = purchase.isAutoRenewing,
|
|
516
|
-
// iOS fields
|
|
517
732
|
quantityIOS = null,
|
|
518
733
|
originalTransactionDateIOS = null,
|
|
519
734
|
originalTransactionIdentifierIOS = null,
|
|
520
735
|
appAccountToken = null,
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
autoRenewingAndroid = purchase.autoRenewingAndroid,
|
|
736
|
+
purchaseTokenAndroid = androidPurchase?.purchaseToken,
|
|
737
|
+
dataAndroid = androidPurchase?.dataAndroid,
|
|
738
|
+
signatureAndroid = androidPurchase?.signatureAndroid,
|
|
739
|
+
autoRenewingAndroid = androidPurchase?.autoRenewingAndroid,
|
|
526
740
|
purchaseStateAndroid = purchaseStateAndroidNumeric,
|
|
527
|
-
isAcknowledgedAndroid =
|
|
528
|
-
packageNameAndroid =
|
|
529
|
-
obfuscatedAccountIdAndroid =
|
|
530
|
-
obfuscatedProfileIdAndroid =
|
|
741
|
+
isAcknowledgedAndroid = androidPurchase?.isAcknowledgedAndroid,
|
|
742
|
+
packageNameAndroid = androidPurchase?.packageNameAndroid,
|
|
743
|
+
obfuscatedAccountIdAndroid = androidPurchase?.obfuscatedAccountIdAndroid,
|
|
744
|
+
obfuscatedProfileIdAndroid = androidPurchase?.obfuscatedProfileIdAndroid
|
|
531
745
|
)
|
|
532
746
|
}
|
|
533
747
|
|
|
534
|
-
private fun mapPurchaseState(state:
|
|
535
|
-
return when (state
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
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
|
|
542
756
|
}
|
|
543
757
|
}
|
|
544
758
|
|
|
@@ -563,9 +777,12 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
563
777
|
return Promise.async {
|
|
564
778
|
try {
|
|
565
779
|
initConnection().await()
|
|
566
|
-
|
|
780
|
+
RnIapLog.payload("getStorefrontAndroid", null)
|
|
781
|
+
val value = openIap.getStorefront()
|
|
782
|
+
RnIapLog.result("getStorefrontAndroid", value)
|
|
783
|
+
value
|
|
567
784
|
} catch (e: Exception) {
|
|
568
|
-
|
|
785
|
+
RnIapLog.failure("getStorefrontAndroid", e)
|
|
569
786
|
""
|
|
570
787
|
}
|
|
571
788
|
}
|
|
@@ -580,13 +797,21 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
580
797
|
skuAndroid = options.skuAndroid,
|
|
581
798
|
packageNameAndroid = options.packageNameAndroid
|
|
582
799
|
).let { openIap.deepLinkToSubscriptions(it) }
|
|
800
|
+
RnIapLog.result("deepLinkToSubscriptionsAndroid", true)
|
|
583
801
|
} catch (e: Exception) {
|
|
584
|
-
|
|
802
|
+
RnIapLog.failure("deepLinkToSubscriptionsAndroid", e)
|
|
585
803
|
throw e
|
|
586
804
|
}
|
|
587
805
|
}
|
|
588
806
|
}
|
|
589
807
|
|
|
808
|
+
// iOS-specific method - not supported on Android
|
|
809
|
+
override fun getPromotedProductIOS(): Promise<NitroProduct?> {
|
|
810
|
+
return Promise.async {
|
|
811
|
+
null
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
|
|
590
815
|
// iOS-specific method - not supported on Android
|
|
591
816
|
override fun requestPromotedProductIOS(): Promise<NitroProduct?> {
|
|
592
817
|
return Promise.async {
|
|
@@ -634,6 +859,12 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
634
859
|
}
|
|
635
860
|
}
|
|
636
861
|
|
|
862
|
+
override fun deepLinkToSubscriptionsIOS(): Promise<Boolean> {
|
|
863
|
+
return Promise.async {
|
|
864
|
+
false
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
|
|
637
868
|
// Receipt validation
|
|
638
869
|
override fun validateReceipt(params: NitroReceiptValidationParams): Promise<Variant_NitroReceiptValidationResultIOS_NitroReceiptValidationResultAndroid> {
|
|
639
870
|
return Promise.async {
|
|
@@ -734,7 +965,19 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
734
965
|
throw Exception(toErrorJson(OpenIAPError.NotSupported))
|
|
735
966
|
}
|
|
736
967
|
}
|
|
737
|
-
|
|
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
|
+
|
|
738
981
|
override fun isTransactionVerifiedIOS(sku: String): Promise<Boolean> {
|
|
739
982
|
return Promise.async {
|
|
740
983
|
throw Exception(toErrorJson(OpenIAPError.NotSupported))
|
|
@@ -756,9 +999,10 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
756
999
|
debugMessage: String? = null,
|
|
757
1000
|
messageOverride: String? = null
|
|
758
1001
|
): String {
|
|
759
|
-
val code = OpenIAPError.toCode(error)
|
|
1002
|
+
val code = OpenIAPError.Companion.toCode(error)
|
|
760
1003
|
val message = messageOverride?.takeIf { it.isNotBlank() }
|
|
761
|
-
?: error.message
|
|
1004
|
+
?: error.message?.takeIf { it.isNotBlank() }
|
|
1005
|
+
?: OpenIAPError.Companion.defaultMessage(code)
|
|
762
1006
|
return BillingUtils.createErrorJson(
|
|
763
1007
|
code = code,
|
|
764
1008
|
message = message,
|
|
@@ -774,9 +1018,10 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
774
1018
|
debugMessage: String? = null,
|
|
775
1019
|
messageOverride: String? = null
|
|
776
1020
|
): NitroPurchaseResult {
|
|
777
|
-
val code = OpenIAPError.toCode(error)
|
|
1021
|
+
val code = OpenIAPError.Companion.toCode(error)
|
|
778
1022
|
val message = messageOverride?.takeIf { it.isNotBlank() }
|
|
779
|
-
?: error.message
|
|
1023
|
+
?: error.message?.takeIf { it.isNotBlank() }
|
|
1024
|
+
?: OpenIAPError.Companion.defaultMessage(code)
|
|
780
1025
|
return NitroPurchaseResult(
|
|
781
1026
|
responseCode = -1.0,
|
|
782
1027
|
debugMessage = debugMessage ?: error.message,
|