expo-iap 3.0.7 → 3.1.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/CLAUDE.md +14 -2
- package/CONTRIBUTING.md +19 -0
- package/README.md +18 -6
- package/android/build.gradle +24 -1
- package/android/src/main/java/expo/modules/iap/ExpoIapLog.kt +69 -0
- package/android/src/main/java/expo/modules/iap/ExpoIapModule.kt +190 -59
- package/build/index.d.ts +32 -111
- package/build/index.d.ts.map +1 -1
- package/build/index.js +198 -243
- package/build/index.js.map +1 -1
- package/build/modules/android.d.ts +7 -12
- package/build/modules/android.d.ts.map +1 -1
- package/build/modules/android.js +15 -12
- package/build/modules/android.js.map +1 -1
- package/build/modules/ios.d.ts +35 -36
- package/build/modules/ios.d.ts.map +1 -1
- package/build/modules/ios.js +101 -35
- package/build/modules/ios.js.map +1 -1
- package/build/types.d.ts +107 -82
- package/build/types.d.ts.map +1 -1
- package/build/types.js +1 -0
- package/build/types.js.map +1 -1
- package/build/useIAP.d.ts +7 -12
- package/build/useIAP.d.ts.map +1 -1
- package/build/useIAP.js +49 -23
- package/build/useIAP.js.map +1 -1
- package/build/utils/errorMapping.d.ts +32 -23
- package/build/utils/errorMapping.d.ts.map +1 -1
- package/build/utils/errorMapping.js +117 -22
- package/build/utils/errorMapping.js.map +1 -1
- package/ios/ExpoIap.podspec +3 -2
- package/ios/ExpoIapHelper.swift +96 -0
- package/ios/ExpoIapLog.swift +127 -0
- package/ios/ExpoIapModule.swift +218 -340
- package/openiap-versions.json +5 -0
- package/package.json +2 -2
- package/plugin/build/withIAP.js +6 -4
- package/plugin/src/withIAP.ts +14 -4
- package/scripts/update-types.mjs +20 -1
- package/src/index.ts +280 -356
- package/src/modules/android.ts +25 -23
- package/src/modules/ios.ts +138 -48
- package/src/types.ts +139 -91
- package/src/useIAP.ts +91 -58
- package/src/utils/errorMapping.ts +203 -23
- package/.copilot-instructions.md +0 -321
- package/.cursorrules +0 -321
- package/build/purchase-error.d.ts +0 -67
- package/build/purchase-error.d.ts.map +0 -1
- package/build/purchase-error.js +0 -166
- package/build/purchase-error.js.map +0 -1
- package/src/purchase-error.ts +0 -265
|
@@ -2,12 +2,22 @@ package expo.modules.iap
|
|
|
2
2
|
|
|
3
3
|
import android.content.Context
|
|
4
4
|
import android.util.Log
|
|
5
|
+
import dev.hyo.openiap.AndroidSubscriptionOfferInput
|
|
6
|
+
import dev.hyo.openiap.DeepLinkOptions
|
|
7
|
+
import dev.hyo.openiap.FetchProductsResultProducts
|
|
8
|
+
import dev.hyo.openiap.FetchProductsResultSubscriptions
|
|
5
9
|
import dev.hyo.openiap.OpenIapError
|
|
6
10
|
import dev.hyo.openiap.OpenIapModule
|
|
7
|
-
import dev.hyo.openiap.
|
|
8
|
-
import dev.hyo.openiap.
|
|
9
|
-
import dev.hyo.openiap.
|
|
10
|
-
import dev.hyo.openiap.
|
|
11
|
+
import dev.hyo.openiap.ProductQueryType
|
|
12
|
+
import dev.hyo.openiap.ProductRequest
|
|
13
|
+
import dev.hyo.openiap.Purchase
|
|
14
|
+
import dev.hyo.openiap.RequestPurchaseAndroidProps
|
|
15
|
+
import dev.hyo.openiap.RequestPurchaseProps
|
|
16
|
+
import dev.hyo.openiap.RequestPurchasePropsByPlatforms
|
|
17
|
+
import dev.hyo.openiap.RequestPurchaseResultPurchase
|
|
18
|
+
import dev.hyo.openiap.RequestPurchaseResultPurchases
|
|
19
|
+
import dev.hyo.openiap.RequestSubscriptionAndroidProps
|
|
20
|
+
import dev.hyo.openiap.RequestSubscriptionPropsByPlatforms
|
|
11
21
|
import expo.modules.kotlin.Promise
|
|
12
22
|
import expo.modules.kotlin.exception.Exceptions
|
|
13
23
|
import expo.modules.kotlin.modules.Module
|
|
@@ -18,6 +28,7 @@ import kotlinx.coroutines.Job
|
|
|
18
28
|
import kotlinx.coroutines.launch
|
|
19
29
|
import kotlinx.coroutines.sync.Mutex
|
|
20
30
|
import kotlinx.coroutines.sync.withLock
|
|
31
|
+
import java.util.Locale
|
|
21
32
|
import java.util.concurrent.ConcurrentLinkedQueue
|
|
22
33
|
import java.util.concurrent.atomic.AtomicBoolean
|
|
23
34
|
|
|
@@ -59,7 +70,20 @@ class ExpoIapModule : Module() {
|
|
|
59
70
|
pendingEvents.add(name to payload)
|
|
60
71
|
}
|
|
61
72
|
|
|
62
|
-
|
|
73
|
+
private fun parseProductQueryType(rawType: String?): ProductQueryType {
|
|
74
|
+
val normalized =
|
|
75
|
+
rawType
|
|
76
|
+
?.trim()
|
|
77
|
+
?.lowercase(Locale.US)
|
|
78
|
+
?.replace("-", "")
|
|
79
|
+
?.replace("_", "")
|
|
80
|
+
|
|
81
|
+
return when (normalized) {
|
|
82
|
+
"subs" -> ProductQueryType.Subs
|
|
83
|
+
"all" -> ProductQueryType.All
|
|
84
|
+
else -> ProductQueryType.InApp
|
|
85
|
+
}
|
|
86
|
+
}
|
|
63
87
|
|
|
64
88
|
override fun definition() =
|
|
65
89
|
ModuleDefinition {
|
|
@@ -72,6 +96,7 @@ class ExpoIapModule : Module() {
|
|
|
72
96
|
Events(EVENT_PURCHASE_UPDATED, EVENT_PURCHASE_ERROR)
|
|
73
97
|
|
|
74
98
|
AsyncFunction("initConnection") { promise: Promise ->
|
|
99
|
+
ExpoIapLog.payload("initConnection", null)
|
|
75
100
|
scope.launch {
|
|
76
101
|
connectionMutex.withLock {
|
|
77
102
|
try {
|
|
@@ -82,6 +107,7 @@ class ExpoIapModule : Module() {
|
|
|
82
107
|
|
|
83
108
|
// If already connected, short-circuit
|
|
84
109
|
if (connectionReady.get()) {
|
|
110
|
+
ExpoIapLog.result("initConnection", true)
|
|
85
111
|
promise.resolve(true)
|
|
86
112
|
return@withLock
|
|
87
113
|
}
|
|
@@ -90,8 +116,9 @@ class ExpoIapModule : Module() {
|
|
|
90
116
|
if (!listenersAttached) {
|
|
91
117
|
listenersAttached = true
|
|
92
118
|
openIap.addPurchaseUpdateListener { p ->
|
|
93
|
-
runCatching {
|
|
94
|
-
|
|
119
|
+
runCatching {
|
|
120
|
+
emitOrQueue(EVENT_PURCHASE_UPDATED, p.toJson())
|
|
121
|
+
}.onFailure { Log.e(TAG, "Failed to buffer/send PURCHASE_UPDATED", it) }
|
|
95
122
|
}
|
|
96
123
|
openIap.addPurchaseErrorListener { e ->
|
|
97
124
|
runCatching { emitOrQueue(EVENT_PURCHASE_ERROR, e.toJSON()) }
|
|
@@ -104,6 +131,10 @@ class ExpoIapModule : Module() {
|
|
|
104
131
|
if (!ok) {
|
|
105
132
|
// Clear any buffered events from a failed init
|
|
106
133
|
pendingEvents.clear()
|
|
134
|
+
ExpoIapLog.failure(
|
|
135
|
+
"initConnection",
|
|
136
|
+
IllegalStateException("Failed to initialize connection"),
|
|
137
|
+
)
|
|
107
138
|
promise.reject(OpenIapError.InitConnection.CODE, "Failed to initialize connection", null)
|
|
108
139
|
return@withLock
|
|
109
140
|
}
|
|
@@ -117,8 +148,10 @@ class ExpoIapModule : Module() {
|
|
|
117
148
|
.onFailure { Log.e(TAG, "Failed to flush buffered event: ${ev.first}", it) }
|
|
118
149
|
}
|
|
119
150
|
|
|
151
|
+
ExpoIapLog.result("initConnection", true)
|
|
120
152
|
promise.resolve(true)
|
|
121
153
|
} catch (e: Exception) {
|
|
154
|
+
ExpoIapLog.failure("initConnection", e)
|
|
122
155
|
promise.reject(OpenIapError.InitConnection.CODE, e.message, e)
|
|
123
156
|
}
|
|
124
157
|
}
|
|
@@ -126,35 +159,54 @@ class ExpoIapModule : Module() {
|
|
|
126
159
|
}
|
|
127
160
|
|
|
128
161
|
AsyncFunction("endConnection") { promise: Promise ->
|
|
162
|
+
ExpoIapLog.payload("endConnection", null)
|
|
129
163
|
scope.launch {
|
|
130
164
|
connectionMutex.withLock {
|
|
131
165
|
runCatching { openIap.endConnection() }
|
|
132
166
|
// Reset connection state and clear any buffered events
|
|
133
167
|
connectionReady.set(false)
|
|
134
168
|
pendingEvents.clear()
|
|
169
|
+
ExpoIapLog.result("endConnection", true)
|
|
135
170
|
promise.resolve(true)
|
|
136
171
|
}
|
|
137
172
|
}
|
|
138
173
|
}
|
|
139
174
|
|
|
140
175
|
AsyncFunction("fetchProducts") { type: String, skuArr: Array<String>, promise: Promise ->
|
|
176
|
+
ExpoIapLog.payload(
|
|
177
|
+
"fetchProductsAndroid",
|
|
178
|
+
mapOf("type" to type, "skus" to skuArr.toList()),
|
|
179
|
+
)
|
|
141
180
|
scope.launch {
|
|
142
181
|
try {
|
|
143
|
-
val
|
|
144
|
-
val
|
|
145
|
-
|
|
182
|
+
val queryType = parseProductQueryType(type)
|
|
183
|
+
val request = ProductRequest(skuArr.toList(), queryType)
|
|
184
|
+
val result = openIap.fetchProducts(request)
|
|
185
|
+
val payload =
|
|
186
|
+
when (result) {
|
|
187
|
+
is FetchProductsResultProducts -> result.value.orEmpty().map { it.toJson() }
|
|
188
|
+
is FetchProductsResultSubscriptions -> result.value.orEmpty().map { it.toJson() }
|
|
189
|
+
else -> emptyList<Map<String, Any?>>()
|
|
190
|
+
}
|
|
191
|
+
ExpoIapLog.result("fetchProductsAndroid", payload)
|
|
192
|
+
promise.resolve(payload)
|
|
146
193
|
} catch (e: Exception) {
|
|
194
|
+
ExpoIapLog.failure("fetchProductsAndroid", e)
|
|
147
195
|
promise.reject(OpenIapError.QueryProduct.CODE, e.message, null)
|
|
148
196
|
}
|
|
149
197
|
}
|
|
150
198
|
}
|
|
151
199
|
|
|
152
200
|
AsyncFunction("getAvailableItems") { promise: Promise ->
|
|
201
|
+
ExpoIapLog.payload("getAvailableItemsAndroid", null)
|
|
153
202
|
scope.launch {
|
|
154
203
|
try {
|
|
155
204
|
val purchases = openIap.getAvailablePurchases(null)
|
|
156
|
-
|
|
205
|
+
val payload = purchases.map { it.toJson() }
|
|
206
|
+
ExpoIapLog.result("getAvailableItemsAndroid", payload)
|
|
207
|
+
promise.resolve(payload)
|
|
157
208
|
} catch (e: Exception) {
|
|
209
|
+
ExpoIapLog.failure("getAvailableItemsAndroid", e)
|
|
158
210
|
promise.reject(OpenIapError.ServiceUnavailable.CODE, e.message, null)
|
|
159
211
|
}
|
|
160
212
|
}
|
|
@@ -164,11 +216,22 @@ class ExpoIapModule : Module() {
|
|
|
164
216
|
AsyncFunction("deepLinkToSubscriptionsAndroid") { params: Map<String, Any?>, promise: Promise ->
|
|
165
217
|
val sku = (params["sku"] ?: params["skuAndroid"]) as? String
|
|
166
218
|
val packageName = (params["packageName"] ?: params["packageNameAndroid"]) as? String
|
|
219
|
+
ExpoIapLog.payload(
|
|
220
|
+
"deepLinkToSubscriptionsAndroid",
|
|
221
|
+
mapOf("sku" to sku, "packageName" to packageName),
|
|
222
|
+
)
|
|
167
223
|
scope.launch {
|
|
168
224
|
try {
|
|
169
|
-
openIap.deepLinkToSubscriptions(
|
|
225
|
+
openIap.deepLinkToSubscriptions(
|
|
226
|
+
DeepLinkOptions(
|
|
227
|
+
packageNameAndroid = packageName,
|
|
228
|
+
skuAndroid = sku,
|
|
229
|
+
),
|
|
230
|
+
)
|
|
231
|
+
ExpoIapLog.result("deepLinkToSubscriptionsAndroid", true)
|
|
170
232
|
promise.resolve(null)
|
|
171
233
|
} catch (e: Exception) {
|
|
234
|
+
ExpoIapLog.failure("deepLinkToSubscriptionsAndroid", e)
|
|
172
235
|
promise.reject(OpenIapError.ServiceUnavailable.CODE, e.message, null)
|
|
173
236
|
}
|
|
174
237
|
}
|
|
@@ -176,18 +239,22 @@ class ExpoIapModule : Module() {
|
|
|
176
239
|
|
|
177
240
|
// Get Google Play storefront country code (Android)
|
|
178
241
|
AsyncFunction("getStorefrontAndroid") { promise: Promise ->
|
|
242
|
+
ExpoIapLog.payload("getStorefrontAndroid", null)
|
|
179
243
|
scope.launch {
|
|
180
244
|
try {
|
|
181
245
|
val code = openIap.getStorefront()
|
|
246
|
+
ExpoIapLog.result("getStorefrontAndroid", code)
|
|
182
247
|
promise.resolve(code)
|
|
183
248
|
} catch (e: Exception) {
|
|
249
|
+
ExpoIapLog.failure("getStorefrontAndroid", e)
|
|
184
250
|
promise.reject(OpenIapError.ServiceUnavailable.CODE, e.message, e)
|
|
185
251
|
}
|
|
186
252
|
}
|
|
187
253
|
}
|
|
188
254
|
|
|
189
255
|
AsyncFunction("requestPurchase") { params: Map<String, Any?>, promise: Promise ->
|
|
190
|
-
|
|
256
|
+
ExpoIapLog.payload("requestPurchaseAndroid", params)
|
|
257
|
+
val type = params["type"] as? String
|
|
191
258
|
val skus: List<String> =
|
|
192
259
|
(params["skus"] as? List<*>)?.filterIsInstance<String>()
|
|
193
260
|
?: (params["skuArr"] as? List<*>)?.filterIsInstance<String>()
|
|
@@ -199,7 +266,7 @@ class ExpoIapModule : Module() {
|
|
|
199
266
|
val isOfferPersonalized = params["isOfferPersonalized"] as? Boolean ?: false
|
|
200
267
|
val offerTokenArr =
|
|
201
268
|
(params["offerTokenArr"] as? List<*>)?.filterIsInstance<String>() ?: emptyList()
|
|
202
|
-
val
|
|
269
|
+
val explicitSubscriptionOffers =
|
|
203
270
|
(params["subscriptionOffers"] as? List<*>)?.mapNotNull { rawOffer ->
|
|
204
271
|
val offerMap = rawOffer as? Map<*, *> ?: return@mapNotNull null
|
|
205
272
|
val sku = offerMap["sku"] as? String
|
|
@@ -207,67 +274,123 @@ class ExpoIapModule : Module() {
|
|
|
207
274
|
if (sku.isNullOrEmpty() || offerToken.isNullOrEmpty()) {
|
|
208
275
|
null
|
|
209
276
|
} else {
|
|
210
|
-
|
|
277
|
+
AndroidSubscriptionOfferInput(offerToken = offerToken, sku = sku)
|
|
211
278
|
}
|
|
212
279
|
} ?: emptyList()
|
|
280
|
+
val purchaseToken =
|
|
281
|
+
(params["purchaseTokenAndroid"] ?: params["purchaseToken"]) as? String
|
|
282
|
+
val replacementMode =
|
|
283
|
+
(params["replacementModeAndroid"] ?: params["replacementMode"]) as? Number
|
|
213
284
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
if (token.isNotEmpty()) {
|
|
226
|
-
RequestSubscriptionAndroidProps.SubscriptionOffer(
|
|
227
|
-
sku = sku,
|
|
228
|
-
offerToken = token,
|
|
229
|
-
)
|
|
230
|
-
} else {
|
|
231
|
-
null
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
else -> emptyList()
|
|
235
|
-
}
|
|
285
|
+
val productType =
|
|
286
|
+
when (parseProductQueryType(type)) {
|
|
287
|
+
ProductQueryType.Subs -> ProductQueryType.Subs
|
|
288
|
+
else -> ProductQueryType.InApp
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
val fallbackOffers =
|
|
292
|
+
if (explicitSubscriptionOffers.isEmpty() && offerTokenArr.isNotEmpty()) {
|
|
293
|
+
skus.zip(offerTokenArr).mapNotNull { (sku, token) ->
|
|
294
|
+
if (token.isNotEmpty()) {
|
|
295
|
+
AndroidSubscriptionOfferInput(offerToken = token, sku = sku)
|
|
236
296
|
} else {
|
|
237
|
-
|
|
297
|
+
null
|
|
238
298
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
299
|
+
}
|
|
300
|
+
} else {
|
|
301
|
+
emptyList()
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
val subscriptionOffers =
|
|
305
|
+
(explicitSubscriptionOffers.ifEmpty { fallbackOffers })
|
|
306
|
+
.takeIf { it.isNotEmpty() }
|
|
307
|
+
|
|
308
|
+
val requestProps =
|
|
309
|
+
when (productType) {
|
|
310
|
+
ProductQueryType.Subs -> {
|
|
311
|
+
val android =
|
|
312
|
+
RequestSubscriptionAndroidProps(
|
|
313
|
+
isOfferPersonalized = isOfferPersonalized,
|
|
243
314
|
obfuscatedAccountIdAndroid = obfuscatedAccountId,
|
|
244
315
|
obfuscatedProfileIdAndroid = obfuscatedProfileId,
|
|
245
|
-
|
|
316
|
+
purchaseTokenAndroid = purchaseToken,
|
|
317
|
+
replacementModeAndroid = replacementMode?.toInt(),
|
|
318
|
+
skus = skus,
|
|
246
319
|
subscriptionOffers = subscriptionOffers,
|
|
247
|
-
)
|
|
248
|
-
|
|
320
|
+
)
|
|
321
|
+
RequestPurchaseProps(
|
|
322
|
+
request =
|
|
323
|
+
RequestPurchaseProps.Request.Subscription(
|
|
324
|
+
RequestSubscriptionPropsByPlatforms(android = android),
|
|
325
|
+
),
|
|
326
|
+
type = ProductQueryType.Subs,
|
|
249
327
|
)
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
else -> {
|
|
331
|
+
val android =
|
|
332
|
+
RequestPurchaseAndroidProps(
|
|
333
|
+
isOfferPersonalized = isOfferPersonalized,
|
|
334
|
+
obfuscatedAccountIdAndroid = obfuscatedAccountId,
|
|
335
|
+
obfuscatedProfileIdAndroid = obfuscatedProfileId,
|
|
336
|
+
skus = skus,
|
|
337
|
+
)
|
|
338
|
+
RequestPurchaseProps(
|
|
339
|
+
request =
|
|
340
|
+
RequestPurchaseProps.Request.Purchase(
|
|
341
|
+
RequestPurchasePropsByPlatforms(android = android),
|
|
342
|
+
),
|
|
343
|
+
type = ProductQueryType.InApp,
|
|
344
|
+
)
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
PromiseUtils.addPromiseForKey(PromiseUtils.PROMISE_BUY_ITEM, promise)
|
|
349
|
+
scope.launch {
|
|
350
|
+
try {
|
|
351
|
+
openIap.setActivity(currentActivity)
|
|
352
|
+
val result = openIap.requestPurchase(requestProps)
|
|
353
|
+
val purchases =
|
|
354
|
+
when (result) {
|
|
355
|
+
is RequestPurchaseResultPurchases -> result.value.orEmpty()
|
|
356
|
+
is RequestPurchaseResultPurchase -> result.value?.let(::listOf).orEmpty()
|
|
357
|
+
else -> emptyList()
|
|
358
|
+
}
|
|
359
|
+
ExpoIapLog.result(
|
|
360
|
+
"requestPurchaseAndroid",
|
|
361
|
+
purchases.map { it.toJson() },
|
|
362
|
+
)
|
|
363
|
+
purchases.forEach { purchase ->
|
|
364
|
+
runCatching {
|
|
365
|
+
emitOrQueue(EVENT_PURCHASE_UPDATED, purchase.toJson())
|
|
366
|
+
}.onFailure { ex ->
|
|
367
|
+
Log.e(
|
|
368
|
+
TAG,
|
|
369
|
+
"Failed to send PURCHASE_UPDATED event (requestPurchase)",
|
|
370
|
+
ex,
|
|
371
|
+
)
|
|
255
372
|
}
|
|
256
373
|
}
|
|
257
|
-
PromiseUtils.resolvePromisesForKey(
|
|
374
|
+
PromiseUtils.resolvePromisesForKey(
|
|
375
|
+
PromiseUtils.PROMISE_BUY_ITEM,
|
|
376
|
+
purchases.map { it.toJson() },
|
|
377
|
+
)
|
|
258
378
|
} catch (e: Exception) {
|
|
379
|
+
ExpoIapLog.failure("requestPurchaseAndroid", e)
|
|
259
380
|
val errorMap =
|
|
260
381
|
mapOf(
|
|
261
382
|
"code" to OpenIapError.PurchaseFailed.CODE,
|
|
262
383
|
"message" to (e.message ?: "Purchase failed"),
|
|
263
384
|
"platform" to "android",
|
|
264
385
|
)
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
386
|
+
runCatching { emitOrQueue(EVENT_PURCHASE_ERROR, errorMap) }
|
|
387
|
+
.onFailure { ex ->
|
|
388
|
+
Log.e(
|
|
389
|
+
TAG,
|
|
390
|
+
"Failed to send PURCHASE_ERROR event (requestPurchase)",
|
|
391
|
+
ex,
|
|
392
|
+
)
|
|
393
|
+
}
|
|
271
394
|
PromiseUtils.rejectPromisesForKey(
|
|
272
395
|
PromiseUtils.PROMISE_BUY_ITEM,
|
|
273
396
|
OpenIapError.PurchaseFailed.CODE,
|
|
@@ -279,11 +402,15 @@ class ExpoIapModule : Module() {
|
|
|
279
402
|
}
|
|
280
403
|
|
|
281
404
|
AsyncFunction("acknowledgePurchaseAndroid") { token: String, promise: Promise ->
|
|
405
|
+
ExpoIapLog.payload("acknowledgePurchaseAndroid", mapOf("token" to token))
|
|
282
406
|
scope.launch {
|
|
283
407
|
try {
|
|
284
408
|
openIap.acknowledgePurchaseAndroid(token)
|
|
285
|
-
|
|
409
|
+
val response = mapOf("responseCode" to 0)
|
|
410
|
+
ExpoIapLog.result("acknowledgePurchaseAndroid", response)
|
|
411
|
+
promise.resolve(response)
|
|
286
412
|
} catch (e: Exception) {
|
|
413
|
+
ExpoIapLog.failure("acknowledgePurchaseAndroid", e)
|
|
287
414
|
promise.reject(OpenIapError.ServiceUnavailable.CODE, e.message, null)
|
|
288
415
|
}
|
|
289
416
|
}
|
|
@@ -291,11 +418,15 @@ class ExpoIapModule : Module() {
|
|
|
291
418
|
|
|
292
419
|
// New name: consumePurchaseAndroid
|
|
293
420
|
AsyncFunction("consumePurchaseAndroid") { token: String, promise: Promise ->
|
|
421
|
+
ExpoIapLog.payload("consumePurchaseAndroid", mapOf("token" to token))
|
|
294
422
|
scope.launch {
|
|
295
423
|
try {
|
|
296
424
|
openIap.consumePurchaseAndroid(token)
|
|
297
|
-
|
|
425
|
+
val response = mapOf("responseCode" to 0, "purchaseToken" to token)
|
|
426
|
+
ExpoIapLog.result("consumePurchaseAndroid", response)
|
|
427
|
+
promise.resolve(response)
|
|
298
428
|
} catch (e: Exception) {
|
|
429
|
+
ExpoIapLog.failure("consumePurchaseAndroid", e)
|
|
299
430
|
promise.reject(OpenIapError.ServiceUnavailable.CODE, e.message, null)
|
|
300
431
|
}
|
|
301
432
|
}
|
package/build/index.d.ts
CHANGED
|
@@ -1,17 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { PurchaseError } from './
|
|
1
|
+
import type { MutationField, Product, ProductQueryType, Purchase, QueryField } from './types';
|
|
2
|
+
import { type PurchaseError } from './utils/errorMapping';
|
|
3
3
|
export * from './types';
|
|
4
|
-
export { ErrorCodeUtils, ErrorCodeMapping } from './purchase-error';
|
|
5
4
|
export * from './modules/android';
|
|
6
5
|
export * from './modules/ios';
|
|
7
6
|
export { getActiveSubscriptions, hasActiveSubscriptions, } from './helpers/subscription';
|
|
8
|
-
export declare const PI: any;
|
|
9
7
|
export declare enum OpenIapEvent {
|
|
10
8
|
PurchaseUpdated = "purchase-updated",
|
|
11
9
|
PurchaseError = "purchase-error",
|
|
12
10
|
PromotedProductIOS = "promoted-product-ios"
|
|
13
11
|
}
|
|
14
|
-
export declare function setValueAsync(value: string): any;
|
|
15
12
|
type ExpoIapEventPayloads = {
|
|
16
13
|
[OpenIapEvent.PurchaseUpdated]: Purchase;
|
|
17
14
|
[OpenIapEvent.PurchaseError]: PurchaseError;
|
|
@@ -28,24 +25,7 @@ export declare const emitter: ExpoIapEmitter;
|
|
|
28
25
|
/**
|
|
29
26
|
* TODO(v3.1.0): Remove legacy 'inapp' alias once downstream apps migrate to 'in-app'.
|
|
30
27
|
*/
|
|
31
|
-
export type ProductTypeInput =
|
|
32
|
-
export type InAppTypeInput = Exclude<ProductTypeInput, 'subs'>;
|
|
33
|
-
type PurchaseRequestInApp = {
|
|
34
|
-
request: RequestPurchasePropsByPlatforms;
|
|
35
|
-
type?: InAppTypeInput;
|
|
36
|
-
};
|
|
37
|
-
type PurchaseRequestSubscription = {
|
|
38
|
-
request: RequestSubscriptionPropsByPlatforms;
|
|
39
|
-
type: 'subs';
|
|
40
|
-
};
|
|
41
|
-
export type PurchaseRequestInput = PurchaseRequestInApp | PurchaseRequestSubscription;
|
|
42
|
-
export type PurchaseRequest = {
|
|
43
|
-
request: RequestPurchaseProps;
|
|
44
|
-
type?: InAppTypeInput;
|
|
45
|
-
} | {
|
|
46
|
-
request: RequestSubscriptionPropsByPlatforms;
|
|
47
|
-
type: 'subs';
|
|
48
|
-
};
|
|
28
|
+
export type ProductTypeInput = ProductQueryType | 'inapp';
|
|
49
29
|
export declare const purchaseUpdatedListener: (listener: (event: Purchase) => void) => {
|
|
50
30
|
remove: () => void;
|
|
51
31
|
};
|
|
@@ -75,55 +55,18 @@ export declare const purchaseErrorListener: (listener: (error: PurchaseError) =>
|
|
|
75
55
|
export declare const promotedProductListenerIOS: (listener: (product: Product) => void) => {
|
|
76
56
|
remove: () => void;
|
|
77
57
|
};
|
|
78
|
-
export declare
|
|
79
|
-
export declare
|
|
58
|
+
export declare const initConnection: MutationField<'initConnection'>;
|
|
59
|
+
export declare const endConnection: MutationField<'endConnection'>;
|
|
80
60
|
/**
|
|
81
61
|
* Fetch products with unified API (v2.7.0+)
|
|
82
62
|
*
|
|
83
|
-
* @param
|
|
84
|
-
* @param
|
|
85
|
-
* @param
|
|
86
|
-
*
|
|
87
|
-
* @example
|
|
88
|
-
* ```typescript
|
|
89
|
-
* // Regular products
|
|
90
|
-
* const products = await fetchProducts({
|
|
91
|
-
* skus: ['product1', 'product2'],
|
|
92
|
-
* type: 'in-app'
|
|
93
|
-
* });
|
|
94
|
-
*
|
|
95
|
-
* // Subscriptions
|
|
96
|
-
* const subscriptions = await fetchProducts({
|
|
97
|
-
* skus: ['sub1', 'sub2'],
|
|
98
|
-
* type: 'subs'
|
|
99
|
-
* });
|
|
100
|
-
* ```
|
|
63
|
+
* @param request - Product fetch configuration
|
|
64
|
+
* @param request.skus - Array of product SKUs to fetch
|
|
65
|
+
* @param request.type - Product query type: 'in-app', 'subs', or 'all'
|
|
101
66
|
*/
|
|
102
|
-
export declare const fetchProducts:
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}) => Promise<Product[] | ProductSubscription[]>;
|
|
106
|
-
export declare const getAvailablePurchases: ({ alsoPublishToEventListenerIOS, onlyIncludeActiveItemsIOS, }?: {
|
|
107
|
-
alsoPublishToEventListenerIOS?: boolean;
|
|
108
|
-
onlyIncludeActiveItemsIOS?: boolean;
|
|
109
|
-
}) => Promise<Purchase[]>;
|
|
110
|
-
/**
|
|
111
|
-
* Restore completed transactions (cross-platform behavior)
|
|
112
|
-
*
|
|
113
|
-
* - iOS: perform a lightweight sync to refresh transactions and ignore sync errors,
|
|
114
|
-
* then fetch available purchases to surface restored items to the app.
|
|
115
|
-
* - Android: simply fetch available purchases (restoration happens via query).
|
|
116
|
-
*
|
|
117
|
-
* This helper returns the restored/available purchases so callers can update UI/state.
|
|
118
|
-
*
|
|
119
|
-
* @param options.alsoPublishToEventListenerIOS - iOS only: whether to also publish to the event listener
|
|
120
|
-
* @param options.onlyIncludeActiveItemsIOS - iOS only: whether to only include active items
|
|
121
|
-
* @returns Promise resolving to the list of available/restored purchases
|
|
122
|
-
*/
|
|
123
|
-
export declare const restorePurchases: (options?: {
|
|
124
|
-
alsoPublishToEventListenerIOS?: boolean;
|
|
125
|
-
onlyIncludeActiveItemsIOS?: boolean;
|
|
126
|
-
}) => Promise<Purchase[]>;
|
|
67
|
+
export declare const fetchProducts: QueryField<'fetchProducts'>;
|
|
68
|
+
export declare const getAvailablePurchases: QueryField<'getAvailablePurchases'>;
|
|
69
|
+
export declare const getStorefront: QueryField<'getStorefrontIOS'>;
|
|
127
70
|
/**
|
|
128
71
|
* Request a purchase for products or subscriptions.
|
|
129
72
|
*
|
|
@@ -155,48 +98,19 @@ export declare const restorePurchases: (options?: {
|
|
|
155
98
|
* });
|
|
156
99
|
* ```
|
|
157
100
|
*/
|
|
158
|
-
export declare const requestPurchase:
|
|
159
|
-
export declare const finishTransaction:
|
|
160
|
-
purchase: Purchase;
|
|
161
|
-
isConsumable?: boolean;
|
|
162
|
-
}) => Promise<VoidResult | boolean>;
|
|
101
|
+
export declare const requestPurchase: MutationField<'requestPurchase'>;
|
|
102
|
+
export declare const finishTransaction: MutationField<'finishTransaction'>;
|
|
163
103
|
/**
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
* @returns Promise resolving to the storefront country code
|
|
167
|
-
* @throws Error if called on non-iOS platform
|
|
168
|
-
*
|
|
169
|
-
* @example
|
|
170
|
-
* ```typescript
|
|
171
|
-
* const storefront = await getStorefrontIOS();
|
|
172
|
-
* console.log(storefront); // 'US'
|
|
173
|
-
* ```
|
|
174
|
-
*
|
|
175
|
-
* @platform iOS
|
|
176
|
-
*/
|
|
177
|
-
export declare const getStorefrontIOS: () => Promise<string>;
|
|
178
|
-
/**
|
|
179
|
-
* Gets the storefront country code from the underlying native store.
|
|
180
|
-
* Returns a two-letter country code such as 'US', 'KR', or empty string on failure.
|
|
104
|
+
* Restore completed transactions (cross-platform behavior)
|
|
181
105
|
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
|
|
185
|
-
export declare const getStorefront: () => Promise<string>;
|
|
186
|
-
/**
|
|
187
|
-
* Internal receipt validation function (NOT RECOMMENDED for production use)
|
|
106
|
+
* - iOS: perform a lightweight sync to refresh transactions and ignore sync errors,
|
|
107
|
+
* then fetch available purchases to surface restored items to the app.
|
|
108
|
+
* - Android: simply fetch available purchases (restoration happens via query).
|
|
188
109
|
*
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
* - iOS: Send receipt data to Apple's verification endpoint from your server
|
|
192
|
-
* - Android: Use Google Play Developer API with service account credentials
|
|
110
|
+
* This helper triggers the refresh flows but does not return the purchases; consumers should
|
|
111
|
+
* call `getAvailablePurchases` or rely on hook state to inspect the latest items.
|
|
193
112
|
*/
|
|
194
|
-
export declare const
|
|
195
|
-
packageName: string;
|
|
196
|
-
productToken: string;
|
|
197
|
-
accessToken: string;
|
|
198
|
-
isSub?: boolean;
|
|
199
|
-
}) => Promise<ReceiptValidationResult>;
|
|
113
|
+
export declare const restorePurchases: MutationField<'restorePurchases'>;
|
|
200
114
|
/**
|
|
201
115
|
* Deeplinks to native interface that allows users to manage their subscriptions
|
|
202
116
|
* @param options.skuAndroid - Required for Android to locate specific subscription (ignored on iOS)
|
|
@@ -215,10 +129,17 @@ export declare const validateReceipt: (sku: string, androidOptions?: {
|
|
|
215
129
|
* packageNameAndroid: 'com.example.app'
|
|
216
130
|
* });
|
|
217
131
|
*/
|
|
218
|
-
export declare const deepLinkToSubscriptions:
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
132
|
+
export declare const deepLinkToSubscriptions: MutationField<'deepLinkToSubscriptions'>;
|
|
133
|
+
/**
|
|
134
|
+
* Internal receipt validation function (NOT RECOMMENDED for production use)
|
|
135
|
+
*
|
|
136
|
+
* WARNING: This function performs client-side validation which is NOT secure.
|
|
137
|
+
* For production apps, always validate receipts on your secure server:
|
|
138
|
+
* - iOS: Send receipt data to Apple's verification endpoint from your server
|
|
139
|
+
* - Android: Use Google Play Developer API with service account credentials
|
|
140
|
+
*/
|
|
141
|
+
export declare const validateReceipt: MutationField<'validateReceipt'>;
|
|
222
142
|
export * from './useIAP';
|
|
223
|
-
export
|
|
143
|
+
export { ErrorCodeUtils, ErrorCodeMapping, createPurchaseError, createPurchaseErrorFromPlatform, } from './utils/errorMapping';
|
|
144
|
+
export type { PurchaseError as ExpoPurchaseError, PurchaseErrorProps, } from './utils/errorMapping';
|
|
224
145
|
//# sourceMappingURL=index.d.ts.map
|
package/build/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|