expo-iap 2.9.5 → 2.9.7
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/.eslintrc.js +24 -0
- package/CHANGELOG.md +24 -0
- package/android/src/main/java/expo/modules/iap/ExpoIapModule.kt +271 -289
- package/build/useIAP.d.ts +1 -1
- package/build/useIAP.d.ts.map +1 -1
- package/build/useIAP.js +1 -1
- package/build/useIAP.js.map +1 -1
- package/ios/ExpoIapModule.swift +40 -20
- package/package.json +1 -1
- package/src/useIAP.ts +1 -1
package/.eslintrc.js
CHANGED
|
@@ -5,5 +5,29 @@ module.exports = {
|
|
|
5
5
|
rules: {
|
|
6
6
|
'eslint-comments/no-unlimited-disable': 0,
|
|
7
7
|
'eslint-comments/no-unused-disable': 0,
|
|
8
|
+
// Prevent ambiguous imports that Metro may mis-resolve
|
|
9
|
+
'no-restricted-imports': [
|
|
10
|
+
'error',
|
|
11
|
+
{
|
|
12
|
+
paths: [
|
|
13
|
+
{
|
|
14
|
+
name: '.',
|
|
15
|
+
message:
|
|
16
|
+
"Avoid `import from '.'`; use './index' or an explicit path.",
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
'no-restricted-modules': [
|
|
22
|
+
'error',
|
|
23
|
+
{
|
|
24
|
+
paths: [
|
|
25
|
+
{
|
|
26
|
+
name: '.',
|
|
27
|
+
message: "Avoid `require('.')`; use './index' or an explicit path.",
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
},
|
|
31
|
+
],
|
|
8
32
|
},
|
|
9
33
|
};
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## [2.9.7] - 2025-09-12
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- Android: remove `ensureConnection` wrapper in favor of `BillingClient` auto-reconnect and a simpler `getBillingClientOrReject` precheck
|
|
8
|
+
- Android: also verify `BillingClient.isReady` before proceeding to avoid sporadic failures
|
|
9
|
+
- Android: drop deprecated product fields from mapping (`displayName`, `name`, `oneTimePurchaseOfferDetails`, `subscriptionOfferDetails`) in favor of `...Android` suffixed fields
|
|
10
|
+
- iOS: add `ensureConnection()` guard to all public async APIs; fix main-actor state updates and minor warnings
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Android: fix stray brace that prematurely closed `ModuleDefinition` and ktlint trailing spaces
|
|
15
|
+
|
|
16
|
+
## [2.9.6] - 2025-09-11
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- Metro bundling error when importing the hook: fix "Unable to resolve '../../..' from node_modules/expo-iap/build/useIAP.js" by changing an ambiguous `import '.'` to an explicit `import './index'` inside `useIAP`. This prevents Metro from walking up to the app root and trying to resolve `expo-router/entry`.
|
|
21
|
+
|
|
22
|
+
### Notes
|
|
23
|
+
|
|
24
|
+
- No runtime behavior changes; this is a bundling path fix only.
|
|
25
|
+
- If you cannot upgrade immediately, temporary workaround: patch `node_modules/expo-iap/build/useIAP.js` to replace `from '.'` with `from './index'`, then clear cache (`npx expo start -c`).
|
|
26
|
+
|
|
3
27
|
## [2.9.5] - 2025-09-10
|
|
4
28
|
|
|
5
29
|
### Changed
|
|
@@ -155,268 +155,248 @@ class ExpoIapModule :
|
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
AsyncFunction("fetchProducts") { type: String, skuArr: Array<String>, promise: Promise ->
|
|
158
|
-
|
|
159
|
-
val skuList =
|
|
160
|
-
skuArr.map { sku ->
|
|
161
|
-
QueryProductDetailsParams.Product
|
|
162
|
-
.newBuilder()
|
|
163
|
-
.setProductId(sku)
|
|
164
|
-
.setProductType(type)
|
|
165
|
-
.build()
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (skuList.isEmpty()) {
|
|
169
|
-
promise.reject(IapErrorCode.E_EMPTY_SKU_LIST, "The SKU list is empty.", null)
|
|
170
|
-
return@ensureConnection
|
|
171
|
-
}
|
|
158
|
+
val billingClient = getBillingClientOrReject(promise) ?: return@AsyncFunction
|
|
172
159
|
|
|
173
|
-
|
|
174
|
-
|
|
160
|
+
val skuList =
|
|
161
|
+
skuArr.map { sku ->
|
|
162
|
+
QueryProductDetailsParams.Product
|
|
175
163
|
.newBuilder()
|
|
176
|
-
.
|
|
164
|
+
.setProductId(sku)
|
|
165
|
+
.setProductType(type)
|
|
177
166
|
.build()
|
|
167
|
+
}
|
|
178
168
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
"Error querying product details: ${billingResult.debugMessage}",
|
|
184
|
-
null,
|
|
185
|
-
)
|
|
186
|
-
return@queryProductDetailsAsync
|
|
187
|
-
}
|
|
169
|
+
if (skuList.isEmpty()) {
|
|
170
|
+
promise.reject(IapErrorCode.E_EMPTY_SKU_LIST, "The SKU list is empty.", null)
|
|
171
|
+
return@AsyncFunction
|
|
172
|
+
}
|
|
188
173
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
val currency = productDetails.oneTimePurchaseOfferDetails?.priceCurrencyCode
|
|
196
|
-
?: productDetails.subscriptionOfferDetails?.firstOrNull()?.pricingPhases?.pricingPhaseList?.firstOrNull()?.priceCurrencyCode
|
|
197
|
-
?: "Unknown"
|
|
198
|
-
val displayPrice = productDetails.oneTimePurchaseOfferDetails?.formattedPrice
|
|
199
|
-
?: productDetails.subscriptionOfferDetails?.firstOrNull()?.pricingPhases?.pricingPhaseList?.firstOrNull()?.formattedPrice
|
|
200
|
-
?: "N/A"
|
|
201
|
-
|
|
202
|
-
// Prepare reusable data
|
|
203
|
-
val oneTimePurchaseData = productDetails.oneTimePurchaseOfferDetails?.let {
|
|
204
|
-
mapOf(
|
|
205
|
-
"priceCurrencyCode" to it.priceCurrencyCode,
|
|
206
|
-
"formattedPrice" to it.formattedPrice,
|
|
207
|
-
"priceAmountMicros" to it.priceAmountMicros.toString(),
|
|
208
|
-
)
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
val subscriptionOfferData = productDetails.subscriptionOfferDetails?.map { subscriptionOfferDetailsItem ->
|
|
212
|
-
mapOf(
|
|
213
|
-
"basePlanId" to subscriptionOfferDetailsItem.basePlanId,
|
|
214
|
-
"offerId" to subscriptionOfferDetailsItem.offerId,
|
|
215
|
-
"offerToken" to subscriptionOfferDetailsItem.offerToken,
|
|
216
|
-
"offerTags" to subscriptionOfferDetailsItem.offerTags,
|
|
217
|
-
"pricingPhases" to
|
|
218
|
-
mapOf(
|
|
219
|
-
"pricingPhaseList" to
|
|
220
|
-
subscriptionOfferDetailsItem.pricingPhases.pricingPhaseList.map
|
|
221
|
-
{ pricingPhaseItem ->
|
|
222
|
-
mapOf(
|
|
223
|
-
"formattedPrice" to pricingPhaseItem.formattedPrice,
|
|
224
|
-
"priceCurrencyCode" to pricingPhaseItem.priceCurrencyCode,
|
|
225
|
-
"billingPeriod" to pricingPhaseItem.billingPeriod,
|
|
226
|
-
"billingCycleCount" to pricingPhaseItem.billingCycleCount,
|
|
227
|
-
"priceAmountMicros" to
|
|
228
|
-
pricingPhaseItem.priceAmountMicros.toString(),
|
|
229
|
-
"recurrenceMode" to pricingPhaseItem.recurrenceMode,
|
|
230
|
-
)
|
|
231
|
-
},
|
|
232
|
-
),
|
|
233
|
-
)
|
|
234
|
-
}
|
|
174
|
+
val params =
|
|
175
|
+
QueryProductDetailsParams
|
|
176
|
+
.newBuilder()
|
|
177
|
+
.setProductList(skuList)
|
|
178
|
+
.build()
|
|
235
179
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
180
|
+
billingClient.queryProductDetailsAsync(params) { billingResult: BillingResult, productDetailsResult: QueryProductDetailsResult ->
|
|
181
|
+
if (billingResult.responseCode != BillingClient.BillingResponseCode.OK) {
|
|
182
|
+
promise.reject(
|
|
183
|
+
IapErrorCode.E_QUERY_PRODUCT,
|
|
184
|
+
"Error querying product details: ${billingResult.debugMessage}",
|
|
185
|
+
null,
|
|
186
|
+
)
|
|
187
|
+
return@queryProductDetailsAsync
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
val productDetailsList = productDetailsResult.productDetailsList ?: emptyList()
|
|
191
|
+
|
|
192
|
+
val items =
|
|
193
|
+
productDetailsList.map { productDetails ->
|
|
194
|
+
skus[productDetails.productId] = productDetails
|
|
195
|
+
|
|
196
|
+
val currency = productDetails.oneTimePurchaseOfferDetails?.priceCurrencyCode
|
|
197
|
+
?: productDetails.subscriptionOfferDetails?.firstOrNull()?.pricingPhases?.pricingPhaseList?.firstOrNull()?.priceCurrencyCode
|
|
198
|
+
?: "Unknown"
|
|
199
|
+
val displayPrice = productDetails.oneTimePurchaseOfferDetails?.formattedPrice
|
|
200
|
+
?: productDetails.subscriptionOfferDetails?.firstOrNull()?.pricingPhases?.pricingPhaseList?.firstOrNull()?.formattedPrice
|
|
201
|
+
?: "N/A"
|
|
202
|
+
|
|
203
|
+
// Prepare reusable data
|
|
204
|
+
val oneTimePurchaseData = productDetails.oneTimePurchaseOfferDetails?.let {
|
|
239
205
|
mapOf(
|
|
240
|
-
"
|
|
241
|
-
"
|
|
242
|
-
"
|
|
243
|
-
"type" to productType,
|
|
244
|
-
// New field names with Android suffix
|
|
245
|
-
"nameAndroid" to productDetails.name,
|
|
246
|
-
"oneTimePurchaseOfferDetailsAndroid" to oneTimePurchaseData,
|
|
247
|
-
"subscriptionOfferDetailsAndroid" to subscriptionOfferData,
|
|
248
|
-
"platform" to "android",
|
|
249
|
-
"currency" to currency,
|
|
250
|
-
"displayPrice" to displayPrice,
|
|
251
|
-
// START: Deprecated - will be removed in v2.9.0
|
|
252
|
-
// Use nameAndroid instead of displayName
|
|
253
|
-
"displayName" to productDetails.name,
|
|
254
|
-
// Use nameAndroid instead of name
|
|
255
|
-
"name" to productDetails.name,
|
|
256
|
-
// Use oneTimePurchaseOfferDetailsAndroid instead of oneTimePurchaseOfferDetails
|
|
257
|
-
"oneTimePurchaseOfferDetails" to oneTimePurchaseData,
|
|
258
|
-
// Use subscriptionOfferDetailsAndroid instead of subscriptionOfferDetails
|
|
259
|
-
"subscriptionOfferDetails" to subscriptionOfferData,
|
|
260
|
-
// END: Deprecated - will be removed in v2.9.0
|
|
206
|
+
"priceCurrencyCode" to it.priceCurrencyCode,
|
|
207
|
+
"formattedPrice" to it.formattedPrice,
|
|
208
|
+
"priceAmountMicros" to it.priceAmountMicros.toString(),
|
|
261
209
|
)
|
|
262
210
|
}
|
|
263
|
-
|
|
264
|
-
|
|
211
|
+
|
|
212
|
+
val subscriptionOfferData = productDetails.subscriptionOfferDetails?.map { subscriptionOfferDetailsItem ->
|
|
213
|
+
mapOf(
|
|
214
|
+
"basePlanId" to subscriptionOfferDetailsItem.basePlanId,
|
|
215
|
+
"offerId" to subscriptionOfferDetailsItem.offerId,
|
|
216
|
+
"offerToken" to subscriptionOfferDetailsItem.offerToken,
|
|
217
|
+
"offerTags" to subscriptionOfferDetailsItem.offerTags,
|
|
218
|
+
"pricingPhases" to
|
|
219
|
+
mapOf(
|
|
220
|
+
"pricingPhaseList" to
|
|
221
|
+
subscriptionOfferDetailsItem.pricingPhases.pricingPhaseList.map
|
|
222
|
+
{ pricingPhaseItem ->
|
|
223
|
+
mapOf(
|
|
224
|
+
"formattedPrice" to pricingPhaseItem.formattedPrice,
|
|
225
|
+
"priceCurrencyCode" to pricingPhaseItem.priceCurrencyCode,
|
|
226
|
+
"billingPeriod" to pricingPhaseItem.billingPeriod,
|
|
227
|
+
"billingCycleCount" to pricingPhaseItem.billingCycleCount,
|
|
228
|
+
"priceAmountMicros" to
|
|
229
|
+
pricingPhaseItem.priceAmountMicros.toString(),
|
|
230
|
+
"recurrenceMode" to pricingPhaseItem.recurrenceMode,
|
|
231
|
+
)
|
|
232
|
+
},
|
|
233
|
+
),
|
|
234
|
+
)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Convert Android productType to our expected 'inapp' or 'subs'
|
|
238
|
+
val productType = if (productDetails.productType == BillingClient.ProductType.SUBS) "subs" else "inapp"
|
|
239
|
+
|
|
240
|
+
mapOf(
|
|
241
|
+
"id" to productDetails.productId,
|
|
242
|
+
"title" to productDetails.title,
|
|
243
|
+
"description" to productDetails.description,
|
|
244
|
+
"type" to productType,
|
|
245
|
+
// New field names with Android suffix
|
|
246
|
+
"nameAndroid" to productDetails.name,
|
|
247
|
+
"oneTimePurchaseOfferDetailsAndroid" to oneTimePurchaseData,
|
|
248
|
+
"subscriptionOfferDetailsAndroid" to subscriptionOfferData,
|
|
249
|
+
"platform" to "android",
|
|
250
|
+
"currency" to currency,
|
|
251
|
+
"displayPrice" to displayPrice,
|
|
252
|
+
|
|
253
|
+
)
|
|
254
|
+
}
|
|
255
|
+
promise.resolve(items)
|
|
265
256
|
}
|
|
266
257
|
}
|
|
267
258
|
|
|
268
259
|
AsyncFunction("requestProducts") { type: String, skuArr: Array<String>, promise: Promise ->
|
|
269
260
|
Log.w("ExpoIap", "WARNING: requestProducts is deprecated. Use fetchProducts instead. The 'request' prefix should only be used for event-based operations. This method will be removed in version 3.0.0.")
|
|
270
|
-
|
|
271
|
-
ensureConnection(promise) { billingClient ->
|
|
272
|
-
val skuList =
|
|
273
|
-
skuArr.map { sku ->
|
|
274
|
-
QueryProductDetailsParams.Product
|
|
275
|
-
.newBuilder()
|
|
276
|
-
.setProductId(sku)
|
|
277
|
-
.setProductType(type)
|
|
278
|
-
.build()
|
|
279
|
-
}
|
|
261
|
+
val billingClient = getBillingClientOrReject(promise) ?: return@AsyncFunction
|
|
280
262
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
val params =
|
|
287
|
-
QueryProductDetailsParams
|
|
263
|
+
val skuList =
|
|
264
|
+
skuArr.map { sku ->
|
|
265
|
+
QueryProductDetailsParams.Product
|
|
288
266
|
.newBuilder()
|
|
289
|
-
.
|
|
267
|
+
.setProductId(sku)
|
|
268
|
+
.setProductType(type)
|
|
290
269
|
.build()
|
|
270
|
+
}
|
|
291
271
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
"Error querying product details: ${billingResult.debugMessage}",
|
|
297
|
-
null,
|
|
298
|
-
)
|
|
299
|
-
return@queryProductDetailsAsync
|
|
300
|
-
}
|
|
272
|
+
if (skuList.isEmpty()) {
|
|
273
|
+
promise.reject(IapErrorCode.E_EMPTY_SKU_LIST, "The SKU list is empty.", null)
|
|
274
|
+
return@AsyncFunction
|
|
275
|
+
}
|
|
301
276
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
"formattedPrice" to it.formattedPrice,
|
|
320
|
-
"priceAmountMicros" to it.priceAmountMicros.toString(),
|
|
321
|
-
)
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
val subscriptionOfferData = productDetails.subscriptionOfferDetails?.map { subscriptionOfferDetailsItem ->
|
|
325
|
-
mapOf(
|
|
326
|
-
"basePlanId" to subscriptionOfferDetailsItem.basePlanId,
|
|
327
|
-
"offerId" to subscriptionOfferDetailsItem.offerId,
|
|
328
|
-
"offerToken" to subscriptionOfferDetailsItem.offerToken,
|
|
329
|
-
"offerTags" to subscriptionOfferDetailsItem.offerTags,
|
|
330
|
-
"pricingPhases" to
|
|
331
|
-
mapOf(
|
|
332
|
-
"pricingPhaseList" to
|
|
333
|
-
subscriptionOfferDetailsItem.pricingPhases.pricingPhaseList.map
|
|
334
|
-
{ pricingPhaseItem ->
|
|
335
|
-
mapOf(
|
|
336
|
-
"formattedPrice" to pricingPhaseItem.formattedPrice,
|
|
337
|
-
"priceCurrencyCode" to pricingPhaseItem.priceCurrencyCode,
|
|
338
|
-
"billingPeriod" to pricingPhaseItem.billingPeriod,
|
|
339
|
-
"billingCycleCount" to pricingPhaseItem.billingCycleCount,
|
|
340
|
-
"priceAmountMicros" to
|
|
341
|
-
pricingPhaseItem.priceAmountMicros.toString(),
|
|
342
|
-
"recurrenceMode" to pricingPhaseItem.recurrenceMode,
|
|
343
|
-
)
|
|
344
|
-
},
|
|
345
|
-
),
|
|
346
|
-
)
|
|
347
|
-
}
|
|
277
|
+
val params =
|
|
278
|
+
QueryProductDetailsParams
|
|
279
|
+
.newBuilder()
|
|
280
|
+
.setProductList(skuList)
|
|
281
|
+
.build()
|
|
282
|
+
|
|
283
|
+
billingClient.queryProductDetailsAsync(params) { billingResult: BillingResult, productDetailsResult: QueryProductDetailsResult ->
|
|
284
|
+
if (billingResult.responseCode != BillingClient.BillingResponseCode.OK) {
|
|
285
|
+
promise.reject(
|
|
286
|
+
IapErrorCode.E_QUERY_PRODUCT,
|
|
287
|
+
"Error querying product details: ${billingResult.debugMessage}",
|
|
288
|
+
null,
|
|
289
|
+
)
|
|
290
|
+
return@queryProductDetailsAsync
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
val productDetailsList = productDetailsResult.productDetailsList ?: emptyList()
|
|
348
294
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
295
|
+
val items =
|
|
296
|
+
productDetailsList.map { productDetails ->
|
|
297
|
+
skus[productDetails.productId] = productDetails
|
|
298
|
+
|
|
299
|
+
val currency = productDetails.oneTimePurchaseOfferDetails?.priceCurrencyCode
|
|
300
|
+
?: productDetails.subscriptionOfferDetails?.firstOrNull()?.pricingPhases?.pricingPhaseList?.firstOrNull()?.priceCurrencyCode
|
|
301
|
+
?: "Unknown"
|
|
302
|
+
val displayPrice = productDetails.oneTimePurchaseOfferDetails?.formattedPrice
|
|
303
|
+
?: productDetails.subscriptionOfferDetails?.firstOrNull()?.pricingPhases?.pricingPhaseList?.firstOrNull()?.formattedPrice
|
|
304
|
+
?: "N/A"
|
|
305
|
+
|
|
306
|
+
// Prepare reusable data
|
|
307
|
+
val oneTimePurchaseData = productDetails.oneTimePurchaseOfferDetails?.let {
|
|
352
308
|
mapOf(
|
|
353
|
-
"
|
|
354
|
-
"
|
|
355
|
-
"
|
|
356
|
-
"type" to productType,
|
|
357
|
-
// New field names with Android suffix
|
|
358
|
-
"nameAndroid" to productDetails.name,
|
|
359
|
-
"oneTimePurchaseOfferDetailsAndroid" to oneTimePurchaseData,
|
|
360
|
-
"subscriptionOfferDetailsAndroid" to subscriptionOfferData,
|
|
361
|
-
"platform" to "android",
|
|
362
|
-
"currency" to currency,
|
|
363
|
-
"displayPrice" to displayPrice,
|
|
364
|
-
// START: Deprecated - will be removed in v2.9.0
|
|
365
|
-
// Use nameAndroid instead of displayName
|
|
366
|
-
"displayName" to productDetails.name,
|
|
367
|
-
// Use nameAndroid instead of name
|
|
368
|
-
"name" to productDetails.name,
|
|
369
|
-
// Use oneTimePurchaseOfferDetailsAndroid instead of oneTimePurchaseOfferDetails
|
|
370
|
-
"oneTimePurchaseOfferDetails" to oneTimePurchaseData,
|
|
371
|
-
// Use subscriptionOfferDetailsAndroid instead of subscriptionOfferDetails
|
|
372
|
-
"subscriptionOfferDetails" to subscriptionOfferData,
|
|
373
|
-
// END: Deprecated - will be removed in v2.9.0
|
|
309
|
+
"priceCurrencyCode" to it.priceCurrencyCode,
|
|
310
|
+
"formattedPrice" to it.formattedPrice,
|
|
311
|
+
"priceAmountMicros" to it.priceAmountMicros.toString(),
|
|
374
312
|
)
|
|
375
313
|
}
|
|
376
|
-
|
|
377
|
-
|
|
314
|
+
|
|
315
|
+
val subscriptionOfferData = productDetails.subscriptionOfferDetails?.map { subscriptionOfferDetailsItem ->
|
|
316
|
+
mapOf(
|
|
317
|
+
"basePlanId" to subscriptionOfferDetailsItem.basePlanId,
|
|
318
|
+
"offerId" to subscriptionOfferDetailsItem.offerId,
|
|
319
|
+
"offerToken" to subscriptionOfferDetailsItem.offerToken,
|
|
320
|
+
"offerTags" to subscriptionOfferDetailsItem.offerTags,
|
|
321
|
+
"pricingPhases" to
|
|
322
|
+
mapOf(
|
|
323
|
+
"pricingPhaseList" to
|
|
324
|
+
subscriptionOfferDetailsItem.pricingPhases.pricingPhaseList.map
|
|
325
|
+
{ pricingPhaseItem ->
|
|
326
|
+
mapOf(
|
|
327
|
+
"formattedPrice" to pricingPhaseItem.formattedPrice,
|
|
328
|
+
"priceCurrencyCode" to pricingPhaseItem.priceCurrencyCode,
|
|
329
|
+
"billingPeriod" to pricingPhaseItem.billingPeriod,
|
|
330
|
+
"billingCycleCount" to pricingPhaseItem.billingCycleCount,
|
|
331
|
+
"priceAmountMicros" to
|
|
332
|
+
pricingPhaseItem.priceAmountMicros.toString(),
|
|
333
|
+
"recurrenceMode" to pricingPhaseItem.recurrenceMode,
|
|
334
|
+
)
|
|
335
|
+
},
|
|
336
|
+
),
|
|
337
|
+
)
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Convert Android productType to our expected 'inapp' or 'subs'
|
|
341
|
+
val productType = if (productDetails.productType == BillingClient.ProductType.SUBS) "subs" else "inapp"
|
|
342
|
+
|
|
343
|
+
mapOf(
|
|
344
|
+
"id" to productDetails.productId,
|
|
345
|
+
"title" to productDetails.title,
|
|
346
|
+
"description" to productDetails.description,
|
|
347
|
+
"type" to productType,
|
|
348
|
+
// New field names with Android suffix
|
|
349
|
+
"nameAndroid" to productDetails.name,
|
|
350
|
+
"oneTimePurchaseOfferDetailsAndroid" to oneTimePurchaseData,
|
|
351
|
+
"subscriptionOfferDetailsAndroid" to subscriptionOfferData,
|
|
352
|
+
"platform" to "android",
|
|
353
|
+
"currency" to currency,
|
|
354
|
+
"displayPrice" to displayPrice,
|
|
355
|
+
|
|
356
|
+
)
|
|
357
|
+
}
|
|
358
|
+
promise.resolve(items)
|
|
378
359
|
}
|
|
379
360
|
}
|
|
380
361
|
|
|
381
362
|
AsyncFunction("getAvailableItemsByType") { type: String, promise: Promise ->
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
}
|
|
416
|
-
items.add(item)
|
|
363
|
+
val billingClient = getBillingClientOrReject(promise) ?: return@AsyncFunction
|
|
364
|
+
val items = mutableListOf<Map<String, Any?>>()
|
|
365
|
+
billingClient.queryPurchasesAsync(
|
|
366
|
+
QueryPurchasesParams
|
|
367
|
+
.newBuilder()
|
|
368
|
+
.setProductType(
|
|
369
|
+
if (type == "subs") BillingClient.ProductType.SUBS else BillingClient.ProductType.INAPP,
|
|
370
|
+
).build(),
|
|
371
|
+
) { billingResult: BillingResult, purchases: List<Purchase>? ->
|
|
372
|
+
if (!isValidResult(billingResult, promise)) return@queryPurchasesAsync
|
|
373
|
+
purchases?.forEach { purchase ->
|
|
374
|
+
val item =
|
|
375
|
+
mutableMapOf<String, Any?>(
|
|
376
|
+
"id" to purchase.orderId,
|
|
377
|
+
"productId" to purchase.products.firstOrNull() as Any?,
|
|
378
|
+
"ids" to purchase.products,
|
|
379
|
+
"transactionId" to purchase.orderId, // @deprecated - use id instead
|
|
380
|
+
"transactionDate" to purchase.purchaseTime.toDouble(),
|
|
381
|
+
"transactionReceipt" to purchase.originalJson,
|
|
382
|
+
"orderId" to purchase.orderId,
|
|
383
|
+
"purchaseTokenAndroid" to purchase.purchaseToken,
|
|
384
|
+
"purchaseToken" to purchase.purchaseToken,
|
|
385
|
+
"developerPayloadAndroid" to purchase.developerPayload,
|
|
386
|
+
"signatureAndroid" to purchase.signature,
|
|
387
|
+
"purchaseStateAndroid" to purchase.purchaseState,
|
|
388
|
+
"isAcknowledgedAndroid" to purchase.isAcknowledged,
|
|
389
|
+
"packageNameAndroid" to purchase.packageName,
|
|
390
|
+
"obfuscatedAccountIdAndroid" to purchase.accountIdentifiers?.obfuscatedAccountId,
|
|
391
|
+
"obfuscatedProfileIdAndroid" to purchase.accountIdentifiers?.obfuscatedProfileId,
|
|
392
|
+
"platform" to "android",
|
|
393
|
+
)
|
|
394
|
+
if (type == BillingClient.ProductType.SUBS) {
|
|
395
|
+
item["autoRenewingAndroid"] = purchase.isAutoRenewing
|
|
417
396
|
}
|
|
418
|
-
|
|
397
|
+
items.add(item)
|
|
419
398
|
}
|
|
399
|
+
promise.resolve(items)
|
|
420
400
|
}
|
|
421
401
|
}
|
|
422
402
|
|
|
@@ -443,7 +423,7 @@ class ExpoIapModule :
|
|
|
443
423
|
return@AsyncFunction
|
|
444
424
|
}
|
|
445
425
|
|
|
446
|
-
|
|
426
|
+
val billingClient = getBillingClientOrReject(promise) ?: return@AsyncFunction
|
|
447
427
|
PromiseUtils.addPromiseForKey(IapConstants.PROMISE_BUY_ITEM, promise)
|
|
448
428
|
|
|
449
429
|
if (type == BillingClient.ProductType.SUBS && skuArr.size != offerTokenArr.size) {
|
|
@@ -461,7 +441,7 @@ class ExpoIapModule :
|
|
|
461
441
|
Log.e(TAG, "Failed to send PURCHASE_ERROR event: ${e.message}")
|
|
462
442
|
}
|
|
463
443
|
promise.reject(IapErrorCode.E_SKU_OFFER_MISMATCH, debugMessage, null)
|
|
464
|
-
return@
|
|
444
|
+
return@AsyncFunction
|
|
465
445
|
}
|
|
466
446
|
|
|
467
447
|
val productParamsList =
|
|
@@ -484,7 +464,7 @@ class ExpoIapModule :
|
|
|
484
464
|
Log.e(TAG, "Failed to send PURCHASE_ERROR event: ${e.message}")
|
|
485
465
|
}
|
|
486
466
|
promise.reject(IapErrorCode.E_SKU_NOT_FOUND, debugMessage, null)
|
|
487
|
-
return@
|
|
467
|
+
return@AsyncFunction
|
|
488
468
|
}
|
|
489
469
|
|
|
490
470
|
val productDetailParams =
|
|
@@ -541,7 +521,7 @@ class ExpoIapModule :
|
|
|
541
521
|
val errorData = PlayUtils.getBillingResponseData(billingResult.responseCode)
|
|
542
522
|
var errorMessage = billingResult.debugMessage ?: errorData.message
|
|
543
523
|
var subResponseCode: Int? = null
|
|
544
|
-
|
|
524
|
+
|
|
545
525
|
// Check for sub-response codes (v8.0.0+)
|
|
546
526
|
try {
|
|
547
527
|
subResponseCode = billingResult.javaClass.getMethod("getSubResponseCode").invoke(billingResult) as? Int
|
|
@@ -555,7 +535,7 @@ class ExpoIapModule :
|
|
|
555
535
|
} catch (e: Exception) {
|
|
556
536
|
// Method doesn't exist in older versions, ignore
|
|
557
537
|
}
|
|
558
|
-
|
|
538
|
+
|
|
559
539
|
// Send error event to match iOS behavior
|
|
560
540
|
val errorMap = mutableMapOf<String, Any?>(
|
|
561
541
|
"responseCode" to billingResult.responseCode,
|
|
@@ -563,37 +543,35 @@ class ExpoIapModule :
|
|
|
563
543
|
"code" to errorData.code,
|
|
564
544
|
"message" to errorMessage
|
|
565
545
|
)
|
|
566
|
-
|
|
546
|
+
|
|
567
547
|
// Add product ID if available
|
|
568
548
|
if (skuArr.isNotEmpty()) {
|
|
569
549
|
errorMap["productId"] = skuArr.first()
|
|
570
550
|
}
|
|
571
|
-
|
|
551
|
+
|
|
572
552
|
// Add sub-response code if available
|
|
573
553
|
subResponseCode?.let {
|
|
574
554
|
if (it != 0) {
|
|
575
555
|
errorMap["subResponseCode"] = it
|
|
576
556
|
}
|
|
577
557
|
}
|
|
578
|
-
|
|
558
|
+
|
|
579
559
|
try {
|
|
580
560
|
sendEvent(OpenIapEvent.PURCHASE_ERROR, errorMap.toMap())
|
|
581
561
|
} catch (e: Exception) {
|
|
582
562
|
Log.e(TAG, "Failed to send PURCHASE_ERROR event: ${e.message}")
|
|
583
563
|
}
|
|
584
|
-
|
|
564
|
+
|
|
585
565
|
promise.reject(errorData.code, errorMessage, null)
|
|
586
|
-
return@
|
|
566
|
+
return@AsyncFunction
|
|
587
567
|
}
|
|
588
|
-
}
|
|
589
568
|
}
|
|
590
569
|
|
|
591
570
|
AsyncFunction("acknowledgePurchaseAndroid") {
|
|
592
571
|
token: String,
|
|
593
572
|
promise: Promise,
|
|
594
573
|
->
|
|
595
|
-
|
|
596
|
-
ensureConnection(promise) { billingClient ->
|
|
574
|
+
val billingClient = getBillingClientOrReject(promise) ?: return@AsyncFunction
|
|
597
575
|
val acknowledgePurchaseParams =
|
|
598
576
|
AcknowledgePurchaseParams
|
|
599
577
|
.newBuilder()
|
|
@@ -617,7 +595,6 @@ class ExpoIapModule :
|
|
|
617
595
|
map["message"] = errorData.message
|
|
618
596
|
promise.resolve(map)
|
|
619
597
|
}
|
|
620
|
-
}
|
|
621
598
|
}
|
|
622
599
|
|
|
623
600
|
AsyncFunction("consumeProductAndroid") {
|
|
@@ -627,25 +604,24 @@ class ExpoIapModule :
|
|
|
627
604
|
|
|
628
605
|
val params = ConsumeParams.newBuilder().setPurchaseToken(token).build()
|
|
629
606
|
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
val map = mutableMapOf<String, Any?>()
|
|
641
|
-
map["responseCode"] = billingResult.responseCode
|
|
642
|
-
map["debugMessage"] = billingResult.debugMessage
|
|
643
|
-
val errorData = PlayUtils.getBillingResponseData(billingResult.responseCode)
|
|
644
|
-
map["code"] = errorData.code
|
|
645
|
-
map["message"] = errorData.message
|
|
646
|
-
map["purchaseTokenAndroid"] = purchaseToken
|
|
647
|
-
promise.resolve(map)
|
|
607
|
+
val billingClient = getBillingClientOrReject(promise) ?: return@AsyncFunction
|
|
608
|
+
billingClient.consumeAsync(params) { billingResult: BillingResult, purchaseToken: String? ->
|
|
609
|
+
if (billingResult.responseCode != BillingClient.BillingResponseCode.OK) {
|
|
610
|
+
PlayUtils.rejectPromiseWithBillingError(
|
|
611
|
+
promise,
|
|
612
|
+
billingResult.responseCode,
|
|
613
|
+
)
|
|
614
|
+
return@consumeAsync
|
|
648
615
|
}
|
|
616
|
+
|
|
617
|
+
val map = mutableMapOf<String, Any?>()
|
|
618
|
+
map["responseCode"] = billingResult.responseCode
|
|
619
|
+
map["debugMessage"] = billingResult.debugMessage
|
|
620
|
+
val errorData = PlayUtils.getBillingResponseData(billingResult.responseCode)
|
|
621
|
+
map["code"] = errorData.code
|
|
622
|
+
map["message"] = errorData.message
|
|
623
|
+
map["purchaseTokenAndroid"] = purchaseToken
|
|
624
|
+
promise.resolve(map)
|
|
649
625
|
}
|
|
650
626
|
}
|
|
651
627
|
|
|
@@ -653,19 +629,18 @@ class ExpoIapModule :
|
|
|
653
629
|
AsyncFunction("getStorefront") {
|
|
654
630
|
promise: Promise,
|
|
655
631
|
->
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
}
|
|
632
|
+
val billingClient = getBillingClientOrReject(promise) ?: return@AsyncFunction
|
|
633
|
+
billingClient.getBillingConfigAsync(
|
|
634
|
+
GetBillingConfigParams.newBuilder().build(),
|
|
635
|
+
BillingConfigResponseListener { result: BillingResult, config: BillingConfig? ->
|
|
636
|
+
if (result.responseCode == BillingClient.BillingResponseCode.OK) {
|
|
637
|
+
promise.safeResolve(config?.countryCode.orEmpty())
|
|
638
|
+
} else {
|
|
639
|
+
val debugMessage = result.debugMessage.orEmpty()
|
|
640
|
+
promise.safeReject(result.responseCode.toString(), debugMessage)
|
|
641
|
+
}
|
|
642
|
+
},
|
|
643
|
+
)
|
|
669
644
|
}
|
|
670
645
|
}
|
|
671
646
|
|
|
@@ -684,18 +659,25 @@ class ExpoIapModule :
|
|
|
684
659
|
return true
|
|
685
660
|
}
|
|
686
661
|
|
|
687
|
-
private fun
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
return
|
|
662
|
+
private fun getBillingClientOrReject(promise: Promise): BillingClient? {
|
|
663
|
+
val client = billingClientCache
|
|
664
|
+
if (client == null) {
|
|
665
|
+
promise.reject(
|
|
666
|
+
IapErrorCode.E_INIT_CONNECTION,
|
|
667
|
+
"Connection not initialized. Call initConnection() first.",
|
|
668
|
+
null,
|
|
669
|
+
)
|
|
670
|
+
return null
|
|
696
671
|
}
|
|
697
|
-
|
|
698
|
-
|
|
672
|
+
if (!client.isReady) {
|
|
673
|
+
promise.reject(
|
|
674
|
+
IapErrorCode.E_INIT_CONNECTION,
|
|
675
|
+
"BillingClient not ready. Wait for initConnection() to complete.",
|
|
676
|
+
null,
|
|
677
|
+
)
|
|
678
|
+
return null
|
|
679
|
+
}
|
|
680
|
+
return client
|
|
699
681
|
}
|
|
700
682
|
|
|
701
683
|
private fun initBillingClient(
|
package/build/useIAP.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ActiveSubscription } from '
|
|
1
|
+
import { type ActiveSubscription } from './index';
|
|
2
2
|
import { Product, Purchase, PurchaseError, PurchaseResult, SubscriptionProduct, RequestPurchaseProps, RequestSubscriptionProps } from './ExpoIap.types';
|
|
3
3
|
type UseIap = {
|
|
4
4
|
connected: boolean;
|
package/build/useIAP.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useIAP.d.ts","sourceRoot":"","sources":["../src/useIAP.ts"],"names":[],"mappings":"AAMA,OAAO,EAaL,KAAK,kBAAkB,EAExB,MAAM,
|
|
1
|
+
{"version":3,"file":"useIAP.d.ts","sourceRoot":"","sources":["../src/useIAP.ts"],"names":[],"mappings":"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"}
|
package/build/useIAP.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { useCallback, useEffect, useState, useRef } from 'react';
|
|
3
3
|
import { Platform } from 'react-native';
|
|
4
4
|
// Internal modules
|
|
5
|
-
import { endConnection, initConnection, purchaseErrorListener, purchaseUpdatedListener, promotedProductListenerIOS, getAvailablePurchases, finishTransaction as finishTransactionInternal, requestPurchase as requestPurchaseInternal, fetchProducts, validateReceipt as validateReceiptInternal, getActiveSubscriptions, hasActiveSubscriptions, restorePurchases, } from '
|
|
5
|
+
import { endConnection, initConnection, purchaseErrorListener, purchaseUpdatedListener, promotedProductListenerIOS, getAvailablePurchases, finishTransaction as finishTransactionInternal, requestPurchase as requestPurchaseInternal, fetchProducts, validateReceipt as validateReceiptInternal, getActiveSubscriptions, hasActiveSubscriptions, restorePurchases, } from './index';
|
|
6
6
|
import { getPromotedProductIOS, requestPurchaseOnPromotedProductIOS, } from './modules/ios';
|
|
7
7
|
// Types
|
|
8
8
|
import { ErrorCode, } from './ExpoIap.types';
|
package/build/useIAP.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useIAP.js","sourceRoot":"","sources":["../src/useIAP.ts"],"names":[],"mappings":"AAAA,wBAAwB;AACxB,OAAO,EAAC,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAC,MAAM,OAAO,CAAC;AAC/D,OAAO,EAAC,QAAQ,EAAC,MAAM,cAAc,CAAC;AAGtC,mBAAmB;AACnB,OAAO,EACL,aAAa,EACb,cAAc,EACd,qBAAqB,EACrB,uBAAuB,EACvB,0BAA0B,EAC1B,qBAAqB,EACrB,iBAAiB,IAAI,yBAAyB,EAC9C,eAAe,IAAI,uBAAuB,EAC1C,aAAa,EACb,eAAe,IAAI,uBAAuB,EAC1C,sBAAsB,EACtB,sBAAsB,EAEtB,gBAAgB,GACjB,MAAM,GAAG,CAAC;AACX,OAAO,EACL,qBAAqB,EACrB,mCAAmC,GACpC,MAAM,eAAe,CAAC;AAEvB,QAAQ;AACR,OAAO,EAQL,SAAS,GACV,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,2BAA2B,EAC3B,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,sBAAsB,CAAC;AAyE9B;;;GAGG;AACH,MAAM,UAAU,MAAM,CAAC,OAAuB;IAC5C,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAU,KAAK,CAAC,CAAC;IAC3D,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAY,EAAE,CAAC,CAAC;IACxD,MAAM,CAAC,mBAAmB,CAAC,GAAG,QAAQ,CAAa,EAAE,CAAC,CAAC;IACvD,MAAM,CAAC,aAAa,EAAE,gBAAgB,CAAC,GAAG,QAAQ,CAAwB,EAAE,CAAC,CAAC;IAC9E,6DAA6D;IAC7D,MAAM,CAAC,kBAAkB,EAAE,qBAAqB,CAAC,GAAG,QAAQ,CAAa,EAAE,CAAC,CAAC;IAC7E,MAAM,CAAC,eAAe,EAAE,kBAAkB,CAAC,GAAG,QAAQ,EAAY,CAAC;IACnE,MAAM,CAAC,kBAAkB,EAAE,qBAAqB,CAAC,GAAG,QAAQ,EAAW,CAAC;IACxE,MAAM,CAAC,oBAAoB,EAAE,uBAAuB,CAAC,GACnD,QAAQ,EAAiB,CAAC;IAC5B,MAAM,CAAC,oBAAoB,CAAC,GAAG,QAAQ,EAAU,CAAC;IAClD,MAAM,CAAC,mBAAmB,EAAE,sBAAsB,CAAC,GAAG,QAAQ,CAE5D,EAAE,CAAC,CAAC;IAEN,MAAM,UAAU,GAAG,MAAM,CAA4B,OAAO,CAAC,CAAC;IAC9D,MAAM,YAAY,GAAG,MAAM,CAAU,KAAK,CAAC,CAAC;IAE5C,0DAA0D;IAC1D,MAAM,uBAAuB,GAAG,WAAW,CACzC,CACE,aAAkB,EAClB,QAAa,EACb,MAA2B,EACtB,EAAE;QACP,MAAM,MAAM,GAAG,CAAC,GAAG,aAAa,CAAC,CAAC;QAClC,QAAQ,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;YAC3B,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAC7B,CAAC,YAAY,EAAE,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,KAAK,MAAM,CAAC,OAAO,CAAC,CAC3D,CAAC;YACF,IAAI,CAAC,WAAW,EAAE,CAAC;gBACjB,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACvB,CAAC;QACH,CAAC,CAAC,CAAC;QACH,OAAO,MAAM,CAAC;IAChB,CAAC,EACD,EAAE,CACH,CAAC;IAEF,SAAS,CAAC,GAAG,EAAE;QACb,UAAU,CAAC,OAAO,GAAG,OAAO,CAAC;IAC/B,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IAEd,SAAS,CAAC,GAAG,EAAE;QACb,YAAY,CAAC,OAAO,GAAG,SAAS,CAAC;IACnC,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC;IAEhB,MAAM,gBAAgB,GAAG,MAAM,CAK5B,EAAE,CAAC,CAAC;IAEP,MAAM,qBAAqB,GAAG,MAAM,CAAwB,EAAE,CAAC,CAAC;IAEhE,SAAS,CAAC,GAAG,EAAE;QACb,qBAAqB,CAAC,OAAO,GAAG,aAAa,CAAC;IAChD,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC;IAEpB,MAAM,oBAAoB,GAAG,WAAW,CAAC,GAAG,EAAE;QAC5C,kBAAkB,CAAC,SAAS,CAAC,CAAC;IAChC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,yBAAyB,GAAG,WAAW,CAAC,GAAG,EAAE;QACjD,uBAAuB,CAAC,SAAS,CAAC,CAAC;IACrC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,mBAAmB,GAAG,WAAW,CACrC,KAAK,EAAE,IAAc,EAAiB,EAAE;QACtC,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,EAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAC,CAAC,CAAC;YAC1D,WAAW,CAAC,CAAC,YAAY,EAAE,EAAE,CAC3B,uBAAuB,CACrB,YAAY,EACZ,MAAmB,EACnB,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,EAAE,CACxB,CACF,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,0BAA0B,EAAE,KAAK,CAAC,CAAC;QACnD,CAAC;IACH,CAAC,EACD,CAAC,uBAAuB,CAAC,CAC1B,CAAC;IAEF,MAAM,wBAAwB,GAAG,WAAW,CAC1C,KAAK,EAAE,IAAc,EAAiB,EAAE;QACtC,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,EAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAC,CAAC,CAAC;YACzD,gBAAgB,CAAC,CAAC,iBAAiB,EAAE,EAAE,CACrC,uBAAuB,CACrB,iBAAiB,EACjB,MAA+B,EAC/B,CAAC,YAAY,EAAE,EAAE,CAAC,YAAY,CAAC,EAAE,CAClC,CACF,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,+BAA+B,EAAE,KAAK,CAAC,CAAC;QACxD,CAAC;IACH,CAAC,EACD,CAAC,uBAAuB,CAAC,CAC1B,CAAC;IAEF,MAAM,qBAAqB,GAAG,WAAW,CACvC,KAAK,EAAE,MAGN,EAAiB,EAAE;QAClB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC;YAE3C,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;gBAC3B,gBAAgB,CAAC,CAAC,iBAAiB,EAAE,EAAE,CACrC,uBAAuB,CACrB,iBAAiB,EACjB,MAA+B,EAC/B,CAAC,YAAY,EAAE,EAAE,CAAC,YAAY,CAAC,EAAE,CAClC,CACF,CAAC;YACJ,CAAC;iBAAM,CAAC;gBACN,WAAW,CAAC,CAAC,YAAY,EAAE,EAAE,CAC3B,uBAAuB,CACrB,YAAY,EACZ,MAAmB,EACnB,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,EAAE,CACxB,CACF,CAAC;YACJ,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,0BAA0B,EAAE,KAAK,CAAC,CAAC;QACnD,CAAC;IACH,CAAC,EACD,CAAC,uBAAuB,CAAC,CAC1B,CAAC;IAEF,MAAM,uBAAuB,GAAG,WAAW,CACzC,KAAK,EAAE,MAGN,EAAiB,EAAE;QAClB,OAAO,CAAC,IAAI,CACV,kKAAkK,CACnK,CAAC;QACF,OAAO,qBAAqB,CAAC,MAAM,CAAC,CAAC;IACvC,CAAC,EACD,CAAC,qBAAqB,CAAC,CACxB,CAAC;IAEF,MAAM,6BAA6B,GAAG,WAAW,CAAC,KAAK,IAAmB,EAAE;QAC1E,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,qBAAqB,CAAC;gBACzC,6BAA6B,EAAE,KAAK;gBACpC,yBAAyB,EAAE,IAAI;aAChC,CAAC,CAAC;YACH,qBAAqB,CAAC,MAAM,CAAC,CAAC;QAChC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,qCAAqC,EAAE,KAAK,CAAC,CAAC;QAC9D,CAAC;IACH,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,8BAA8B,GAAG,WAAW,CAChD,KAAK,EAAE,eAA0B,EAAiB,EAAE;QAClD,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,sBAAsB,CAAC,eAAe,CAAC,CAAC;YAC7D,sBAAsB,CAAC,MAAM,CAAC,CAAC;QACjC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,qCAAqC,EAAE,KAAK,CAAC,CAAC;YAC5D,mCAAmC;QACrC,CAAC;IACH,CAAC,EACD,EAAE,CACH,CAAC;IAEF,MAAM,8BAA8B,GAAG,WAAW,CAChD,KAAK,EAAE,eAA0B,EAAoB,EAAE;QACrD,IAAI,CAAC;YACH,OAAO,MAAM,sBAAsB,CAAC,eAAe,CAAC,CAAC;QACvD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,sCAAsC,EAAE,KAAK,CAAC,CAAC;YAC7D,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC,EACD,EAAE,CACH,CAAC;IAEF,mFAAmF;IAEnF,MAAM,iBAAiB,GAAG,WAAW,CACnC,KAAK,EAAE,EACL,QAAQ,EACR,YAAY,GAIb,EAAqC,EAAE;QACtC,IAAI,CAAC;YACH,OAAO,MAAM,yBAAyB,CAAC;gBACrC,QAAQ;gBACR,YAAY;aACb,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,GAAG,CAAC;QACZ,CAAC;gBAAS,CAAC;YACT,IAAI,QAAQ,CAAC,EAAE,KAAK,eAAe,EAAE,EAAE,EAAE,CAAC;gBACxC,oBAAoB,EAAE,CAAC;YACzB,CAAC;YACD,IAAI,QAAQ,CAAC,EAAE,KAAK,oBAAoB,EAAE,SAAS,EAAE,CAAC;gBACpD,yBAAyB,EAAE,CAAC;YAC9B,CAAC;QACH,CAAC;IACH,CAAC,EACD;QACE,eAAe,EAAE,EAAE;QACnB,oBAAoB,EAAE,SAAS;QAC/B,oBAAoB;QACpB,yBAAyB;KAC1B,CACF,CAAC;IAEF,MAAM,wBAAwB,GAAG,WAAW,CAC1C,KAAK,EAAE,UAAmD,EAAE,EAAE;QAC5D,oBAAoB,EAAE,CAAC;QACvB,yBAAyB,EAAE,CAAC;QAE5B,IAAI,CAAC;YACH,OAAO,MAAM,uBAAuB,CAAC,UAAU,CAAC,CAAC;QACnD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC,EACD,CAAC,oBAAoB,EAAE,yBAAyB,CAAC,CAClD,CAAC;IAEF,MAAM,yBAAyB,GAAG,WAAW,CAC3C,KAAK,EAAE,SAAiB,EAAE,EAAE;QAC1B,IAAI,CAAC;YACH,IAAI,qBAAqB,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,SAAS,CAAC,EAAE,CAAC;gBACtE,MAAM,wBAAwB,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;gBAC5C,MAAM,6BAA6B,EAAE,CAAC;YACxC,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,IAAI,CAAC,wCAAwC,EAAE,KAAK,CAAC,CAAC;QAChE,CAAC;IACH,CAAC,EACD,CAAC,6BAA6B,EAAE,wBAAwB,CAAC,CAC1D,CAAC;IAEF,+DAA+D;IAC/D,6EAA6E;IAC7E,+CAA+C;IAC/C,MAAM,wBAAwB,GAAG,WAAW,CAAC,KAAK,IAAmB,EAAE;QACrE,IAAI,CAAC;YACH,MAAM,SAAS,GAAG,MAAM,gBAAgB,CAAC;gBACvC,6BAA6B,EAAE,KAAK;gBACpC,yBAAyB,EAAE,IAAI;aAChC,CAAC,CAAC;YACH,qBAAqB,CAAC,SAAS,CAAC,CAAC;QACnC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,IAAI,CAAC,8BAA8B,EAAE,KAAK,CAAC,CAAC;QACtD,CAAC;IACH,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,eAAe,GAAG,WAAW,CACjC,KAAK,EACH,GAAW,EACX,cAKC,EACD,EAAE;QACF,OAAO,uBAAuB,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC;IACtD,CAAC,EACD,EAAE,CACH,CAAC;IAEF,MAAM,wBAAwB,GAAG,WAAW,CAAC,KAAK,IAAmB,EAAE;QACrE,6EAA6E;QAC7E,iFAAiF;QACjF,OAAO,CAAC,GAAG,CAAC,8DAA8D,CAAC,CAAC;QAE5E,oFAAoF;QACpF,gBAAgB,CAAC,OAAO,CAAC,cAAc,GAAG,uBAAuB,CAC/D,KAAK,EAAE,QAAkB,EAAE,EAAE;YAC3B,OAAO,CAAC,GAAG,CAAC,+CAA+C,EAAE,QAAQ,CAAC,CAAC;YACvE,uBAAuB,CAAC,SAAS,CAAC,CAAC;YACnC,kBAAkB,CAAC,QAAQ,CAAC,CAAC;YAE7B,IAAI,mBAAmB,IAAI,QAAQ,EAAE,CAAC;gBACpC,MAAM,yBAAyB,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;YAC/C,CAAC;YAED,IAAI,UAAU,CAAC,OAAO,EAAE,iBAAiB,EAAE,CAAC;gBAC1C,UAAU,CAAC,OAAO,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC;YACjD,CAAC;QACH,CAAC,CACF,CAAC;QAEF,sFAAsF;QACtF,gBAAgB,CAAC,OAAO,CAAC,aAAa,GAAG,qBAAqB,CAC5D,CAAC,KAAoB,EAAE,EAAE;YACvB,IACE,CAAC,YAAY,CAAC,OAAO;gBACrB,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,iBAAiB,EAC1C,CAAC;gBACD,OAAO,CAAC,+CAA+C;YACzD,CAAC;YACD,MAAM,QAAQ,GAAG,2BAA2B,CAAC,KAAK,CAAC,CAAC;YACpD,OAAO,CAAC,GAAG,CAAC,6CAA6C,EAAE,KAAK,CAAC,CAAC;YAClE,IAAI,oBAAoB,CAAC,KAAK,CAAC,EAAE,CAAC;gBAChC,OAAO,CAAC,GAAG,CAAC,kCAAkC,CAAC,CAAC;YAClD,CAAC;iBAAM,IAAI,kBAAkB,CAAC,KAAK,CAAC,EAAE,CAAC;gBACrC,OAAO,CAAC,GAAG,CAAC,sCAAsC,EAAE,QAAQ,CAAC,CAAC;YAChE,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,IAAI,CAAC,0BAA0B,EAAE,QAAQ,CAAC,CAAC;YACrD,CAAC;YACD,kBAAkB,CAAC,SAAS,CAAC,CAAC;YAC9B,uBAAuB,CAAC,KAAK,CAAC,CAAC;YAE/B,IAAI,UAAU,CAAC,OAAO,EAAE,eAAe,EAAE,CAAC;gBACxC,UAAU,CAAC,OAAO,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;YAC5C,CAAC;QACH,CAAC,CACF,CAAC;QAEF,IAAI,QAAQ,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;YAC1B,iCAAiC;YACjC,gBAAgB,CAAC,OAAO,CAAC,mBAAmB,GAAG,0BAA0B,CACvE,CAAC,OAAgB,EAAE,EAAE;gBACnB,OAAO,CAAC,GAAG,CAAC,+CAA+C,EAAE,OAAO,CAAC,CAAC;gBACtE,qBAAqB,CAAC,OAAO,CAAC,CAAC;gBAE/B,IAAI,UAAU,CAAC,OAAO,EAAE,oBAAoB,EAAE,CAAC;oBAC7C,UAAU,CAAC,OAAO,CAAC,oBAAoB,CAAC,OAAO,CAAC,CAAC;gBACnD,CAAC;YACH,CAAC,CACF,CAAC;QACJ,CAAC;QAED,OAAO,CAAC,GAAG,CACT,oEAAoE,CACrE,CAAC;QAEF,oDAAoD;QACpD,MAAM,MAAM,GAAG,MAAM,cAAc,EAAE,CAAC;QACtC,YAAY,CAAC,MAAM,CAAC,CAAC;QACrB,OAAO,CAAC,GAAG,CAAC,iCAAiC,EAAE,MAAM,CAAC,CAAC;QACvD,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,2CAA2C;YAC3C,OAAO,CAAC,IAAI,CAAC,sDAAsD,CAAC,CAAC;YACrE,gBAAgB,CAAC,OAAO,CAAC,cAAc,EAAE,MAAM,EAAE,CAAC;YAClD,gBAAgB,CAAC,OAAO,CAAC,mBAAmB,EAAE,MAAM,EAAE,CAAC;YACvD,gBAAgB,CAAC,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC;YACpD,gBAAgB,CAAC,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC;YACzD,uEAAuE;YACvE,OAAO;QACT,CAAC;IACH,CAAC,EAAE,CAAC,yBAAyB,CAAC,CAAC,CAAC;IAEhC,SAAS,CAAC,GAAG,EAAE;QACb,wBAAwB,EAAE,CAAC;QAC3B,MAAM,oBAAoB,GAAG,gBAAgB,CAAC,OAAO,CAAC;QAEtD,OAAO,GAAG,EAAE;YACV,oBAAoB,CAAC,cAAc,EAAE,MAAM,EAAE,CAAC;YAC9C,oBAAoB,CAAC,aAAa,EAAE,MAAM,EAAE,CAAC;YAC7C,oBAAoB,CAAC,mBAAmB,EAAE,MAAM,EAAE,CAAC;YACnD,oBAAoB,CAAC,kBAAkB,EAAE,MAAM,EAAE,CAAC;YAClD,aAAa,EAAE,CAAC;YAChB,YAAY,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,wBAAwB,CAAC,CAAC,CAAC;IAE/B,OAAO;QACL,SAAS;QACT,QAAQ;QACR,mBAAmB;QACnB,oBAAoB;QACpB,aAAa;QACb,iBAAiB;QACjB,kBAAkB;QAClB,eAAe;QACf,oBAAoB;QACpB,kBAAkB;QAClB,mBAAmB;QACnB,oBAAoB;QACpB,yBAAyB;QACzB,qBAAqB,EAAE,6BAA6B;QACpD,aAAa,EAAE,qBAAqB;QACpC,eAAe,EAAE,uBAAuB;QACxC,eAAe,EAAE,wBAAwB;QACzC,eAAe;QACf,gBAAgB,EAAE,wBAAwB;QAC1C,WAAW,EAAE,mBAAmB;QAChC,gBAAgB,EAAE,wBAAwB;QAC1C,qBAAqB;QACrB,mCAAmC;QACnC,sBAAsB,EAAE,8BAA8B;QACtD,sBAAsB,EAAE,8BAA8B;KACvD,CAAC;AACJ,CAAC","sourcesContent":["// External dependencies\nimport {useCallback, useEffect, useState, useRef} from 'react';\nimport {Platform} from 'react-native';\nimport {EventSubscription} from 'expo-modules-core';\n\n// Internal modules\nimport {\n endConnection,\n initConnection,\n purchaseErrorListener,\n purchaseUpdatedListener,\n promotedProductListenerIOS,\n getAvailablePurchases,\n finishTransaction as finishTransactionInternal,\n requestPurchase as requestPurchaseInternal,\n fetchProducts,\n validateReceipt as validateReceiptInternal,\n getActiveSubscriptions,\n hasActiveSubscriptions,\n type ActiveSubscription,\n restorePurchases,\n} from '.';\nimport {\n getPromotedProductIOS,\n requestPurchaseOnPromotedProductIOS,\n} from './modules/ios';\n\n// Types\nimport {\n Product,\n Purchase,\n PurchaseError,\n PurchaseResult,\n SubscriptionProduct,\n RequestPurchaseProps,\n RequestSubscriptionProps,\n ErrorCode,\n} from './ExpoIap.types';\nimport {\n getUserFriendlyErrorMessage,\n isUserCancelledError,\n isRecoverableError,\n} from './utils/errorMapping';\n\ntype UseIap = {\n connected: boolean;\n products: Product[];\n promotedProductsIOS: Purchase[];\n promotedProductIdIOS?: string;\n subscriptions: SubscriptionProduct[];\n availablePurchases: Purchase[];\n currentPurchase?: Purchase;\n currentPurchaseError?: PurchaseError;\n promotedProductIOS?: Product;\n activeSubscriptions: ActiveSubscription[];\n clearCurrentPurchase: () => void;\n clearCurrentPurchaseError: () => void;\n finishTransaction: ({\n purchase,\n isConsumable,\n }: {\n purchase: Purchase;\n isConsumable?: boolean;\n }) => Promise<PurchaseResult | boolean>;\n getAvailablePurchases: (skus: string[]) => Promise<void>;\n fetchProducts: (params: {\n skus: string[];\n type?: 'inapp' | 'subs';\n }) => Promise<void>;\n /**\n * @deprecated Use fetchProducts({ skus, type: 'inapp' | 'subs' }) instead. This method will be removed in version 3.0.0.\n * The 'request' prefix should only be used for event-based operations.\n */\n requestProducts: (params: {\n skus: string[];\n type?: 'inapp' | 'subs';\n }) => Promise<void>;\n /**\n * @deprecated Use fetchProducts({ skus, type: 'inapp' }) instead. This method will be removed in version 3.0.0.\n * Note: This method internally uses fetchProducts, so no deprecation warning is shown.\n */\n getProducts: (skus: string[]) => Promise<void>;\n /**\n * @deprecated Use fetchProducts({ skus, type: 'subs' }) instead. This method will be removed in version 3.0.0.\n * Note: This method internally uses fetchProducts, so no deprecation warning is shown.\n */\n getSubscriptions: (skus: string[]) => Promise<void>;\n requestPurchase: (params: {\n request: RequestPurchaseProps | RequestSubscriptionProps;\n type?: 'inapp' | 'subs';\n }) => Promise<any>;\n validateReceipt: (\n sku: string,\n androidOptions?: {\n packageName: string;\n productToken: string;\n accessToken: string;\n isSub?: boolean;\n },\n ) => Promise<any>;\n restorePurchases: () => Promise<void>;\n getPromotedProductIOS: () => Promise<Product | null>;\n requestPurchaseOnPromotedProductIOS: () => Promise<void>;\n getActiveSubscriptions: (subscriptionIds?: string[]) => Promise<void>;\n hasActiveSubscriptions: (subscriptionIds?: string[]) => Promise<boolean>;\n};\n\nexport interface UseIAPOptions {\n onPurchaseSuccess?: (purchase: Purchase) => void;\n onPurchaseError?: (error: PurchaseError) => void;\n onSyncError?: (error: Error) => void;\n shouldAutoSyncPurchases?: boolean; // New option to control auto-syncing\n onPromotedProductIOS?: (product: Product) => void;\n}\n\n/**\n * React Hook for managing In-App Purchases.\n * See documentation at https://hyochan.github.io/expo-iap/docs/hooks/useIAP\n */\nexport function useIAP(options?: UseIAPOptions): UseIap {\n const [connected, setConnected] = useState<boolean>(false);\n const [products, setProducts] = useState<Product[]>([]);\n const [promotedProductsIOS] = useState<Purchase[]>([]);\n const [subscriptions, setSubscriptions] = useState<SubscriptionProduct[]>([]);\n // Removed in v2.9.0: purchaseHistories state and related API\n const [availablePurchases, setAvailablePurchases] = useState<Purchase[]>([]);\n const [currentPurchase, setCurrentPurchase] = useState<Purchase>();\n const [promotedProductIOS, setPromotedProductIOS] = useState<Product>();\n const [currentPurchaseError, setCurrentPurchaseError] =\n useState<PurchaseError>();\n const [promotedProductIdIOS] = useState<string>();\n const [activeSubscriptions, setActiveSubscriptions] = useState<\n ActiveSubscription[]\n >([]);\n\n const optionsRef = useRef<UseIAPOptions | undefined>(options);\n const connectedRef = useRef<boolean>(false);\n\n // Helper function to merge arrays with duplicate checking\n const mergeWithDuplicateCheck = useCallback(\n <T>(\n existingItems: T[],\n newItems: T[],\n getKey: (item: T) => string,\n ): T[] => {\n const merged = [...existingItems];\n newItems.forEach((newItem) => {\n const isDuplicate = merged.some(\n (existingItem) => getKey(existingItem) === getKey(newItem),\n );\n if (!isDuplicate) {\n merged.push(newItem);\n }\n });\n return merged;\n },\n [],\n );\n\n useEffect(() => {\n optionsRef.current = options;\n }, [options]);\n\n useEffect(() => {\n connectedRef.current = connected;\n }, [connected]);\n\n const subscriptionsRef = useRef<{\n purchaseUpdate?: EventSubscription;\n purchaseError?: EventSubscription;\n promotedProductsIOS?: EventSubscription;\n promotedProductIOS?: EventSubscription;\n }>({});\n\n const subscriptionsRefState = useRef<SubscriptionProduct[]>([]);\n\n useEffect(() => {\n subscriptionsRefState.current = subscriptions;\n }, [subscriptions]);\n\n const clearCurrentPurchase = useCallback(() => {\n setCurrentPurchase(undefined);\n }, []);\n\n const clearCurrentPurchaseError = useCallback(() => {\n setCurrentPurchaseError(undefined);\n }, []);\n\n const getProductsInternal = useCallback(\n async (skus: string[]): Promise<void> => {\n try {\n const result = await fetchProducts({skus, type: 'inapp'});\n setProducts((prevProducts) =>\n mergeWithDuplicateCheck(\n prevProducts,\n result as Product[],\n (product) => product.id,\n ),\n );\n } catch (error) {\n console.error('Error fetching products:', error);\n }\n },\n [mergeWithDuplicateCheck],\n );\n\n const getSubscriptionsInternal = useCallback(\n async (skus: string[]): Promise<void> => {\n try {\n const result = await fetchProducts({skus, type: 'subs'});\n setSubscriptions((prevSubscriptions) =>\n mergeWithDuplicateCheck(\n prevSubscriptions,\n result as SubscriptionProduct[],\n (subscription) => subscription.id,\n ),\n );\n } catch (error) {\n console.error('Error fetching subscriptions:', error);\n }\n },\n [mergeWithDuplicateCheck],\n );\n\n const fetchProductsInternal = useCallback(\n async (params: {\n skus: string[];\n type?: 'inapp' | 'subs';\n }): Promise<void> => {\n try {\n const result = await fetchProducts(params);\n\n if (params.type === 'subs') {\n setSubscriptions((prevSubscriptions) =>\n mergeWithDuplicateCheck(\n prevSubscriptions,\n result as SubscriptionProduct[],\n (subscription) => subscription.id,\n ),\n );\n } else {\n setProducts((prevProducts) =>\n mergeWithDuplicateCheck(\n prevProducts,\n result as Product[],\n (product) => product.id,\n ),\n );\n }\n } catch (error) {\n console.error('Error fetching products:', error);\n }\n },\n [mergeWithDuplicateCheck],\n );\n\n const requestProductsInternal = useCallback(\n async (params: {\n skus: string[];\n type?: 'inapp' | 'subs';\n }): Promise<void> => {\n console.warn(\n \"`requestProducts` is deprecated in useIAP hook. Use the new `fetchProducts` method instead. The 'request' prefix should only be used for event-based operations.\",\n );\n return fetchProductsInternal(params);\n },\n [fetchProductsInternal],\n );\n\n const getAvailablePurchasesInternal = useCallback(async (): Promise<void> => {\n try {\n const result = await getAvailablePurchases({\n alsoPublishToEventListenerIOS: false,\n onlyIncludeActiveItemsIOS: true,\n });\n setAvailablePurchases(result);\n } catch (error) {\n console.error('Error fetching available purchases:', error);\n }\n }, []);\n\n const getActiveSubscriptionsInternal = useCallback(\n async (subscriptionIds?: string[]): Promise<void> => {\n try {\n const result = await getActiveSubscriptions(subscriptionIds);\n setActiveSubscriptions(result);\n } catch (error) {\n console.error('Error getting active subscriptions:', error);\n // Preserve existing state on error\n }\n },\n [],\n );\n\n const hasActiveSubscriptionsInternal = useCallback(\n async (subscriptionIds?: string[]): Promise<boolean> => {\n try {\n return await hasActiveSubscriptions(subscriptionIds);\n } catch (error) {\n console.error('Error checking active subscriptions:', error);\n return false;\n }\n },\n [],\n );\n\n // NOTE: getPurchaseHistories removed in v2.9.0. Use getAvailablePurchases instead.\n\n const finishTransaction = useCallback(\n async ({\n purchase,\n isConsumable,\n }: {\n purchase: Purchase;\n isConsumable?: boolean;\n }): Promise<PurchaseResult | boolean> => {\n try {\n return await finishTransactionInternal({\n purchase,\n isConsumable,\n });\n } catch (err) {\n throw err;\n } finally {\n if (purchase.id === currentPurchase?.id) {\n clearCurrentPurchase();\n }\n if (purchase.id === currentPurchaseError?.productId) {\n clearCurrentPurchaseError();\n }\n }\n },\n [\n currentPurchase?.id,\n currentPurchaseError?.productId,\n clearCurrentPurchase,\n clearCurrentPurchaseError,\n ],\n );\n\n const requestPurchaseWithReset = useCallback(\n async (requestObj: {request: any; type?: 'inapp' | 'subs'}) => {\n clearCurrentPurchase();\n clearCurrentPurchaseError();\n\n try {\n return await requestPurchaseInternal(requestObj);\n } catch (error) {\n throw error;\n }\n },\n [clearCurrentPurchase, clearCurrentPurchaseError],\n );\n\n const refreshSubscriptionStatus = useCallback(\n async (productId: string) => {\n try {\n if (subscriptionsRefState.current.some((sub) => sub.id === productId)) {\n await getSubscriptionsInternal([productId]);\n await getAvailablePurchasesInternal();\n }\n } catch (error) {\n console.warn('Failed to refresh subscription status:', error);\n }\n },\n [getAvailablePurchasesInternal, getSubscriptionsInternal],\n );\n\n // Restore completed transactions with cross-platform behavior.\n // iOS: best-effort sync (ignore sync errors) then fetch available purchases.\n // Android: fetch available purchases directly.\n const restorePurchasesInternal = useCallback(async (): Promise<void> => {\n try {\n const purchases = await restorePurchases({\n alsoPublishToEventListenerIOS: false,\n onlyIncludeActiveItemsIOS: true,\n });\n setAvailablePurchases(purchases);\n } catch (error) {\n console.warn('Failed to restore purchases:', error);\n }\n }, []);\n\n const validateReceipt = useCallback(\n async (\n sku: string,\n androidOptions?: {\n packageName: string;\n productToken: string;\n accessToken: string;\n isSub?: boolean;\n },\n ) => {\n return validateReceiptInternal(sku, androidOptions);\n },\n [],\n );\n\n const initIapWithSubscriptions = useCallback(async (): Promise<void> => {\n // CRITICAL: Register listeners BEFORE initConnection to avoid race condition\n // Events might fire immediately after initConnection, so listeners must be ready\n console.log('[useIAP] Setting up event listeners BEFORE initConnection...');\n\n // Register purchase update listener BEFORE initConnection to avoid race conditions.\n subscriptionsRef.current.purchaseUpdate = purchaseUpdatedListener(\n async (purchase: Purchase) => {\n console.log('[useIAP] Purchase success callback triggered:', purchase);\n setCurrentPurchaseError(undefined);\n setCurrentPurchase(purchase);\n\n if ('expirationDateIOS' in purchase) {\n await refreshSubscriptionStatus(purchase.id);\n }\n\n if (optionsRef.current?.onPurchaseSuccess) {\n optionsRef.current.onPurchaseSuccess(purchase);\n }\n },\n );\n\n // Register purchase error listener EARLY. Ignore init-related errors until connected.\n subscriptionsRef.current.purchaseError = purchaseErrorListener(\n (error: PurchaseError) => {\n if (\n !connectedRef.current &&\n error.code === ErrorCode.E_INIT_CONNECTION\n ) {\n return; // Ignore initialization error before connected\n }\n const friendly = getUserFriendlyErrorMessage(error);\n console.log('[useIAP] Purchase error callback triggered:', error);\n if (isUserCancelledError(error)) {\n console.log('[useIAP] User cancelled purchase');\n } else if (isRecoverableError(error)) {\n console.log('[useIAP] Recoverable purchase error:', friendly);\n } else {\n console.warn('[useIAP] Purchase error:', friendly);\n }\n setCurrentPurchase(undefined);\n setCurrentPurchaseError(error);\n\n if (optionsRef.current?.onPurchaseError) {\n optionsRef.current.onPurchaseError(error);\n }\n },\n );\n\n if (Platform.OS === 'ios') {\n // iOS promoted products listener\n subscriptionsRef.current.promotedProductsIOS = promotedProductListenerIOS(\n (product: Product) => {\n console.log('[useIAP] Promoted product callback triggered:', product);\n setPromotedProductIOS(product);\n\n if (optionsRef.current?.onPromotedProductIOS) {\n optionsRef.current.onPromotedProductIOS(product);\n }\n },\n );\n }\n\n console.log(\n '[useIAP] Event listeners registered, now calling initConnection...',\n );\n\n // NOW call initConnection after listeners are ready\n const result = await initConnection();\n setConnected(result);\n console.log('[useIAP] initConnection result:', result);\n if (!result) {\n // If connection failed, clean up listeners\n console.warn('[useIAP] Connection failed, cleaning up listeners...');\n subscriptionsRef.current.purchaseUpdate?.remove();\n subscriptionsRef.current.promotedProductsIOS?.remove();\n subscriptionsRef.current.purchaseUpdate = undefined;\n subscriptionsRef.current.promotedProductsIOS = undefined;\n // Keep purchaseError listener registered to capture subsequent retries\n return;\n }\n }, [refreshSubscriptionStatus]);\n\n useEffect(() => {\n initIapWithSubscriptions();\n const currentSubscriptions = subscriptionsRef.current;\n\n return () => {\n currentSubscriptions.purchaseUpdate?.remove();\n currentSubscriptions.purchaseError?.remove();\n currentSubscriptions.promotedProductsIOS?.remove();\n currentSubscriptions.promotedProductIOS?.remove();\n endConnection();\n setConnected(false);\n };\n }, [initIapWithSubscriptions]);\n\n return {\n connected,\n products,\n promotedProductsIOS,\n promotedProductIdIOS,\n subscriptions,\n finishTransaction,\n availablePurchases,\n currentPurchase,\n currentPurchaseError,\n promotedProductIOS,\n activeSubscriptions,\n clearCurrentPurchase,\n clearCurrentPurchaseError,\n getAvailablePurchases: getAvailablePurchasesInternal,\n fetchProducts: fetchProductsInternal,\n requestProducts: requestProductsInternal,\n requestPurchase: requestPurchaseWithReset,\n validateReceipt,\n restorePurchases: restorePurchasesInternal,\n getProducts: getProductsInternal,\n getSubscriptions: getSubscriptionsInternal,\n getPromotedProductIOS,\n requestPurchaseOnPromotedProductIOS,\n getActiveSubscriptions: getActiveSubscriptionsInternal,\n hasActiveSubscriptions: hasActiveSubscriptionsInternal,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"useIAP.js","sourceRoot":"","sources":["../src/useIAP.ts"],"names":[],"mappings":"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","sourcesContent":["// External dependencies\nimport {useCallback, useEffect, useState, useRef} from 'react';\nimport {Platform} from 'react-native';\nimport {EventSubscription} from 'expo-modules-core';\n\n// Internal modules\nimport {\n endConnection,\n initConnection,\n purchaseErrorListener,\n purchaseUpdatedListener,\n promotedProductListenerIOS,\n getAvailablePurchases,\n finishTransaction as finishTransactionInternal,\n requestPurchase as requestPurchaseInternal,\n fetchProducts,\n validateReceipt as validateReceiptInternal,\n getActiveSubscriptions,\n hasActiveSubscriptions,\n type ActiveSubscription,\n restorePurchases,\n} from './index';\nimport {\n getPromotedProductIOS,\n requestPurchaseOnPromotedProductIOS,\n} from './modules/ios';\n\n// Types\nimport {\n Product,\n Purchase,\n PurchaseError,\n PurchaseResult,\n SubscriptionProduct,\n RequestPurchaseProps,\n RequestSubscriptionProps,\n ErrorCode,\n} from './ExpoIap.types';\nimport {\n getUserFriendlyErrorMessage,\n isUserCancelledError,\n isRecoverableError,\n} from './utils/errorMapping';\n\ntype UseIap = {\n connected: boolean;\n products: Product[];\n promotedProductsIOS: Purchase[];\n promotedProductIdIOS?: string;\n subscriptions: SubscriptionProduct[];\n availablePurchases: Purchase[];\n currentPurchase?: Purchase;\n currentPurchaseError?: PurchaseError;\n promotedProductIOS?: Product;\n activeSubscriptions: ActiveSubscription[];\n clearCurrentPurchase: () => void;\n clearCurrentPurchaseError: () => void;\n finishTransaction: ({\n purchase,\n isConsumable,\n }: {\n purchase: Purchase;\n isConsumable?: boolean;\n }) => Promise<PurchaseResult | boolean>;\n getAvailablePurchases: (skus: string[]) => Promise<void>;\n fetchProducts: (params: {\n skus: string[];\n type?: 'inapp' | 'subs';\n }) => Promise<void>;\n /**\n * @deprecated Use fetchProducts({ skus, type: 'inapp' | 'subs' }) instead. This method will be removed in version 3.0.0.\n * The 'request' prefix should only be used for event-based operations.\n */\n requestProducts: (params: {\n skus: string[];\n type?: 'inapp' | 'subs';\n }) => Promise<void>;\n /**\n * @deprecated Use fetchProducts({ skus, type: 'inapp' }) instead. This method will be removed in version 3.0.0.\n * Note: This method internally uses fetchProducts, so no deprecation warning is shown.\n */\n getProducts: (skus: string[]) => Promise<void>;\n /**\n * @deprecated Use fetchProducts({ skus, type: 'subs' }) instead. This method will be removed in version 3.0.0.\n * Note: This method internally uses fetchProducts, so no deprecation warning is shown.\n */\n getSubscriptions: (skus: string[]) => Promise<void>;\n requestPurchase: (params: {\n request: RequestPurchaseProps | RequestSubscriptionProps;\n type?: 'inapp' | 'subs';\n }) => Promise<any>;\n validateReceipt: (\n sku: string,\n androidOptions?: {\n packageName: string;\n productToken: string;\n accessToken: string;\n isSub?: boolean;\n },\n ) => Promise<any>;\n restorePurchases: () => Promise<void>;\n getPromotedProductIOS: () => Promise<Product | null>;\n requestPurchaseOnPromotedProductIOS: () => Promise<void>;\n getActiveSubscriptions: (subscriptionIds?: string[]) => Promise<void>;\n hasActiveSubscriptions: (subscriptionIds?: string[]) => Promise<boolean>;\n};\n\nexport interface UseIAPOptions {\n onPurchaseSuccess?: (purchase: Purchase) => void;\n onPurchaseError?: (error: PurchaseError) => void;\n onSyncError?: (error: Error) => void;\n shouldAutoSyncPurchases?: boolean; // New option to control auto-syncing\n onPromotedProductIOS?: (product: Product) => void;\n}\n\n/**\n * React Hook for managing In-App Purchases.\n * See documentation at https://hyochan.github.io/expo-iap/docs/hooks/useIAP\n */\nexport function useIAP(options?: UseIAPOptions): UseIap {\n const [connected, setConnected] = useState<boolean>(false);\n const [products, setProducts] = useState<Product[]>([]);\n const [promotedProductsIOS] = useState<Purchase[]>([]);\n const [subscriptions, setSubscriptions] = useState<SubscriptionProduct[]>([]);\n // Removed in v2.9.0: purchaseHistories state and related API\n const [availablePurchases, setAvailablePurchases] = useState<Purchase[]>([]);\n const [currentPurchase, setCurrentPurchase] = useState<Purchase>();\n const [promotedProductIOS, setPromotedProductIOS] = useState<Product>();\n const [currentPurchaseError, setCurrentPurchaseError] =\n useState<PurchaseError>();\n const [promotedProductIdIOS] = useState<string>();\n const [activeSubscriptions, setActiveSubscriptions] = useState<\n ActiveSubscription[]\n >([]);\n\n const optionsRef = useRef<UseIAPOptions | undefined>(options);\n const connectedRef = useRef<boolean>(false);\n\n // Helper function to merge arrays with duplicate checking\n const mergeWithDuplicateCheck = useCallback(\n <T>(\n existingItems: T[],\n newItems: T[],\n getKey: (item: T) => string,\n ): T[] => {\n const merged = [...existingItems];\n newItems.forEach((newItem) => {\n const isDuplicate = merged.some(\n (existingItem) => getKey(existingItem) === getKey(newItem),\n );\n if (!isDuplicate) {\n merged.push(newItem);\n }\n });\n return merged;\n },\n [],\n );\n\n useEffect(() => {\n optionsRef.current = options;\n }, [options]);\n\n useEffect(() => {\n connectedRef.current = connected;\n }, [connected]);\n\n const subscriptionsRef = useRef<{\n purchaseUpdate?: EventSubscription;\n purchaseError?: EventSubscription;\n promotedProductsIOS?: EventSubscription;\n promotedProductIOS?: EventSubscription;\n }>({});\n\n const subscriptionsRefState = useRef<SubscriptionProduct[]>([]);\n\n useEffect(() => {\n subscriptionsRefState.current = subscriptions;\n }, [subscriptions]);\n\n const clearCurrentPurchase = useCallback(() => {\n setCurrentPurchase(undefined);\n }, []);\n\n const clearCurrentPurchaseError = useCallback(() => {\n setCurrentPurchaseError(undefined);\n }, []);\n\n const getProductsInternal = useCallback(\n async (skus: string[]): Promise<void> => {\n try {\n const result = await fetchProducts({skus, type: 'inapp'});\n setProducts((prevProducts) =>\n mergeWithDuplicateCheck(\n prevProducts,\n result as Product[],\n (product) => product.id,\n ),\n );\n } catch (error) {\n console.error('Error fetching products:', error);\n }\n },\n [mergeWithDuplicateCheck],\n );\n\n const getSubscriptionsInternal = useCallback(\n async (skus: string[]): Promise<void> => {\n try {\n const result = await fetchProducts({skus, type: 'subs'});\n setSubscriptions((prevSubscriptions) =>\n mergeWithDuplicateCheck(\n prevSubscriptions,\n result as SubscriptionProduct[],\n (subscription) => subscription.id,\n ),\n );\n } catch (error) {\n console.error('Error fetching subscriptions:', error);\n }\n },\n [mergeWithDuplicateCheck],\n );\n\n const fetchProductsInternal = useCallback(\n async (params: {\n skus: string[];\n type?: 'inapp' | 'subs';\n }): Promise<void> => {\n try {\n const result = await fetchProducts(params);\n\n if (params.type === 'subs') {\n setSubscriptions((prevSubscriptions) =>\n mergeWithDuplicateCheck(\n prevSubscriptions,\n result as SubscriptionProduct[],\n (subscription) => subscription.id,\n ),\n );\n } else {\n setProducts((prevProducts) =>\n mergeWithDuplicateCheck(\n prevProducts,\n result as Product[],\n (product) => product.id,\n ),\n );\n }\n } catch (error) {\n console.error('Error fetching products:', error);\n }\n },\n [mergeWithDuplicateCheck],\n );\n\n const requestProductsInternal = useCallback(\n async (params: {\n skus: string[];\n type?: 'inapp' | 'subs';\n }): Promise<void> => {\n console.warn(\n \"`requestProducts` is deprecated in useIAP hook. Use the new `fetchProducts` method instead. The 'request' prefix should only be used for event-based operations.\",\n );\n return fetchProductsInternal(params);\n },\n [fetchProductsInternal],\n );\n\n const getAvailablePurchasesInternal = useCallback(async (): Promise<void> => {\n try {\n const result = await getAvailablePurchases({\n alsoPublishToEventListenerIOS: false,\n onlyIncludeActiveItemsIOS: true,\n });\n setAvailablePurchases(result);\n } catch (error) {\n console.error('Error fetching available purchases:', error);\n }\n }, []);\n\n const getActiveSubscriptionsInternal = useCallback(\n async (subscriptionIds?: string[]): Promise<void> => {\n try {\n const result = await getActiveSubscriptions(subscriptionIds);\n setActiveSubscriptions(result);\n } catch (error) {\n console.error('Error getting active subscriptions:', error);\n // Preserve existing state on error\n }\n },\n [],\n );\n\n const hasActiveSubscriptionsInternal = useCallback(\n async (subscriptionIds?: string[]): Promise<boolean> => {\n try {\n return await hasActiveSubscriptions(subscriptionIds);\n } catch (error) {\n console.error('Error checking active subscriptions:', error);\n return false;\n }\n },\n [],\n );\n\n // NOTE: getPurchaseHistories removed in v2.9.0. Use getAvailablePurchases instead.\n\n const finishTransaction = useCallback(\n async ({\n purchase,\n isConsumable,\n }: {\n purchase: Purchase;\n isConsumable?: boolean;\n }): Promise<PurchaseResult | boolean> => {\n try {\n return await finishTransactionInternal({\n purchase,\n isConsumable,\n });\n } catch (err) {\n throw err;\n } finally {\n if (purchase.id === currentPurchase?.id) {\n clearCurrentPurchase();\n }\n if (purchase.id === currentPurchaseError?.productId) {\n clearCurrentPurchaseError();\n }\n }\n },\n [\n currentPurchase?.id,\n currentPurchaseError?.productId,\n clearCurrentPurchase,\n clearCurrentPurchaseError,\n ],\n );\n\n const requestPurchaseWithReset = useCallback(\n async (requestObj: {request: any; type?: 'inapp' | 'subs'}) => {\n clearCurrentPurchase();\n clearCurrentPurchaseError();\n\n try {\n return await requestPurchaseInternal(requestObj);\n } catch (error) {\n throw error;\n }\n },\n [clearCurrentPurchase, clearCurrentPurchaseError],\n );\n\n const refreshSubscriptionStatus = useCallback(\n async (productId: string) => {\n try {\n if (subscriptionsRefState.current.some((sub) => sub.id === productId)) {\n await getSubscriptionsInternal([productId]);\n await getAvailablePurchasesInternal();\n }\n } catch (error) {\n console.warn('Failed to refresh subscription status:', error);\n }\n },\n [getAvailablePurchasesInternal, getSubscriptionsInternal],\n );\n\n // Restore completed transactions with cross-platform behavior.\n // iOS: best-effort sync (ignore sync errors) then fetch available purchases.\n // Android: fetch available purchases directly.\n const restorePurchasesInternal = useCallback(async (): Promise<void> => {\n try {\n const purchases = await restorePurchases({\n alsoPublishToEventListenerIOS: false,\n onlyIncludeActiveItemsIOS: true,\n });\n setAvailablePurchases(purchases);\n } catch (error) {\n console.warn('Failed to restore purchases:', error);\n }\n }, []);\n\n const validateReceipt = useCallback(\n async (\n sku: string,\n androidOptions?: {\n packageName: string;\n productToken: string;\n accessToken: string;\n isSub?: boolean;\n },\n ) => {\n return validateReceiptInternal(sku, androidOptions);\n },\n [],\n );\n\n const initIapWithSubscriptions = useCallback(async (): Promise<void> => {\n // CRITICAL: Register listeners BEFORE initConnection to avoid race condition\n // Events might fire immediately after initConnection, so listeners must be ready\n console.log('[useIAP] Setting up event listeners BEFORE initConnection...');\n\n // Register purchase update listener BEFORE initConnection to avoid race conditions.\n subscriptionsRef.current.purchaseUpdate = purchaseUpdatedListener(\n async (purchase: Purchase) => {\n console.log('[useIAP] Purchase success callback triggered:', purchase);\n setCurrentPurchaseError(undefined);\n setCurrentPurchase(purchase);\n\n if ('expirationDateIOS' in purchase) {\n await refreshSubscriptionStatus(purchase.id);\n }\n\n if (optionsRef.current?.onPurchaseSuccess) {\n optionsRef.current.onPurchaseSuccess(purchase);\n }\n },\n );\n\n // Register purchase error listener EARLY. Ignore init-related errors until connected.\n subscriptionsRef.current.purchaseError = purchaseErrorListener(\n (error: PurchaseError) => {\n if (\n !connectedRef.current &&\n error.code === ErrorCode.E_INIT_CONNECTION\n ) {\n return; // Ignore initialization error before connected\n }\n const friendly = getUserFriendlyErrorMessage(error);\n console.log('[useIAP] Purchase error callback triggered:', error);\n if (isUserCancelledError(error)) {\n console.log('[useIAP] User cancelled purchase');\n } else if (isRecoverableError(error)) {\n console.log('[useIAP] Recoverable purchase error:', friendly);\n } else {\n console.warn('[useIAP] Purchase error:', friendly);\n }\n setCurrentPurchase(undefined);\n setCurrentPurchaseError(error);\n\n if (optionsRef.current?.onPurchaseError) {\n optionsRef.current.onPurchaseError(error);\n }\n },\n );\n\n if (Platform.OS === 'ios') {\n // iOS promoted products listener\n subscriptionsRef.current.promotedProductsIOS = promotedProductListenerIOS(\n (product: Product) => {\n console.log('[useIAP] Promoted product callback triggered:', product);\n setPromotedProductIOS(product);\n\n if (optionsRef.current?.onPromotedProductIOS) {\n optionsRef.current.onPromotedProductIOS(product);\n }\n },\n );\n }\n\n console.log(\n '[useIAP] Event listeners registered, now calling initConnection...',\n );\n\n // NOW call initConnection after listeners are ready\n const result = await initConnection();\n setConnected(result);\n console.log('[useIAP] initConnection result:', result);\n if (!result) {\n // If connection failed, clean up listeners\n console.warn('[useIAP] Connection failed, cleaning up listeners...');\n subscriptionsRef.current.purchaseUpdate?.remove();\n subscriptionsRef.current.promotedProductsIOS?.remove();\n subscriptionsRef.current.purchaseUpdate = undefined;\n subscriptionsRef.current.promotedProductsIOS = undefined;\n // Keep purchaseError listener registered to capture subsequent retries\n return;\n }\n }, [refreshSubscriptionStatus]);\n\n useEffect(() => {\n initIapWithSubscriptions();\n const currentSubscriptions = subscriptionsRef.current;\n\n return () => {\n currentSubscriptions.purchaseUpdate?.remove();\n currentSubscriptions.purchaseError?.remove();\n currentSubscriptions.promotedProductsIOS?.remove();\n currentSubscriptions.promotedProductIOS?.remove();\n endConnection();\n setConnected(false);\n };\n }, [initIapWithSubscriptions]);\n\n return {\n connected,\n products,\n promotedProductsIOS,\n promotedProductIdIOS,\n subscriptions,\n finishTransaction,\n availablePurchases,\n currentPurchase,\n currentPurchaseError,\n promotedProductIOS,\n activeSubscriptions,\n clearCurrentPurchase,\n clearCurrentPurchaseError,\n getAvailablePurchases: getAvailablePurchasesInternal,\n fetchProducts: fetchProductsInternal,\n requestProducts: requestProductsInternal,\n requestPurchase: requestPurchaseWithReset,\n validateReceipt,\n restorePurchases: restorePurchasesInternal,\n getProducts: getProductsInternal,\n getSubscriptions: getSubscriptionsInternal,\n getPromotedProductIOS,\n requestPurchaseOnPromotedProductIOS,\n getActiveSubscriptions: getActiveSubscriptionsInternal,\n hasActiveSubscriptions: hasActiveSubscriptionsInternal,\n };\n}\n"]}
|
package/ios/ExpoIapModule.swift
CHANGED
|
@@ -22,6 +22,8 @@ struct OpenIapEvent {
|
|
|
22
22
|
@available(iOS 15.0, tvOS 15.0, *)
|
|
23
23
|
@MainActor
|
|
24
24
|
public class ExpoIapModule: Module {
|
|
25
|
+
// Connection state for local validation parity with RN module
|
|
26
|
+
private var isInitialized: Bool = false
|
|
25
27
|
// Subscriptions for OpenIapModule event listeners
|
|
26
28
|
private var purchaseUpdatedSub: Subscription?
|
|
27
29
|
private var purchaseErrorSub: Subscription?
|
|
@@ -65,6 +67,8 @@ public class ExpoIapModule: Module {
|
|
|
65
67
|
AsyncFunction("initConnection") { () async throws -> Bool in
|
|
66
68
|
logDebug("initConnection called")
|
|
67
69
|
let isConnected = try await OpenIapModule.shared.initConnection()
|
|
70
|
+
// Track initialization locally for ensureConnection()
|
|
71
|
+
await MainActor.run { self.isInitialized = isConnected }
|
|
68
72
|
logDebug("Connection initialized: \(isConnected)")
|
|
69
73
|
return isConnected
|
|
70
74
|
}
|
|
@@ -74,12 +78,14 @@ public class ExpoIapModule: Module {
|
|
|
74
78
|
let _ = try await OpenIapModule.shared.endConnection()
|
|
75
79
|
|
|
76
80
|
logDebug("Connection ended")
|
|
81
|
+
await MainActor.run { self.isInitialized = false }
|
|
77
82
|
return true
|
|
78
83
|
}
|
|
79
84
|
|
|
80
85
|
// MARK: - Product Management
|
|
81
86
|
|
|
82
87
|
AsyncFunction("fetchProducts") { (params: [String: Any]) async throws -> [[String: Any?]] in
|
|
88
|
+
try await ensureConnection()
|
|
83
89
|
logDebug("fetchProducts raw params: \(params)")
|
|
84
90
|
|
|
85
91
|
// Handle both object format {skus: [...], type: "..."} and array format
|
|
@@ -148,6 +154,7 @@ public class ExpoIapModule: Module {
|
|
|
148
154
|
guard let sku = params["sku"] as? String, !sku.isEmpty else {
|
|
149
155
|
throw OpenIapError.make(code: OpenIapError.E_PURCHASE_ERROR, message: "Missing required 'sku'")
|
|
150
156
|
}
|
|
157
|
+
try await ensureConnection()
|
|
151
158
|
|
|
152
159
|
// Optional fields
|
|
153
160
|
let andFinish = (params["andDangerouslyFinishTransactionAutomatically"] as? Bool) ?? false
|
|
@@ -205,6 +212,7 @@ public class ExpoIapModule: Module {
|
|
|
205
212
|
}
|
|
206
213
|
|
|
207
214
|
AsyncFunction("finishTransaction") { (transactionId: String) async throws -> Bool in
|
|
215
|
+
try await ensureConnection()
|
|
208
216
|
logDebug("finishTransaction called with id: \(transactionId)")
|
|
209
217
|
let result = try await OpenIapModule.shared.finishTransaction(transactionIdentifier: transactionId)
|
|
210
218
|
return result
|
|
@@ -213,6 +221,7 @@ public class ExpoIapModule: Module {
|
|
|
213
221
|
// MARK: - Purchase History
|
|
214
222
|
|
|
215
223
|
AsyncFunction("getAvailablePurchases") { (options: [String: Any?]?) async throws -> [[String: Any?]] in
|
|
224
|
+
try await ensureConnection()
|
|
216
225
|
logDebug("getAvailablePurchases called")
|
|
217
226
|
|
|
218
227
|
// Build options and get purchases directly from OpenIapModule
|
|
@@ -228,6 +237,7 @@ public class ExpoIapModule: Module {
|
|
|
228
237
|
|
|
229
238
|
// Legacy function for backward compatibility
|
|
230
239
|
AsyncFunction("getAvailableItems") { (alsoPublishToEventListener: Bool, onlyIncludeActiveItems: Bool) async throws -> [[String: Any?]] in
|
|
240
|
+
try await ensureConnection()
|
|
231
241
|
logDebug("getAvailableItems called (legacy)")
|
|
232
242
|
|
|
233
243
|
let purchaseOptions = OpenIapGetAvailablePurchasesProps(
|
|
@@ -239,6 +249,7 @@ public class ExpoIapModule: Module {
|
|
|
239
249
|
}
|
|
240
250
|
|
|
241
251
|
AsyncFunction("getPendingTransactionsIOS") { () async throws -> [[String: Any?]] in
|
|
252
|
+
try await ensureConnection()
|
|
242
253
|
logDebug("getPendingTransactionsIOS called")
|
|
243
254
|
|
|
244
255
|
let pendingTransactions = try await OpenIapModule.shared.getPendingTransactionsIOS()
|
|
@@ -246,6 +257,7 @@ public class ExpoIapModule: Module {
|
|
|
246
257
|
}
|
|
247
258
|
|
|
248
259
|
AsyncFunction("clearTransactionIOS") { () async throws -> Bool in
|
|
260
|
+
try await ensureConnection()
|
|
249
261
|
logDebug("clearTransactionIOS called")
|
|
250
262
|
try await OpenIapModule.shared.clearTransactionIOS()
|
|
251
263
|
return true
|
|
@@ -254,17 +266,20 @@ public class ExpoIapModule: Module {
|
|
|
254
266
|
// MARK: - Receipt & Validation
|
|
255
267
|
|
|
256
268
|
AsyncFunction("getReceiptIOS") { () async throws -> String in
|
|
269
|
+
try await ensureConnection()
|
|
257
270
|
logDebug("getReceiptIOS called")
|
|
258
271
|
return try await OpenIapModule.shared.getReceiptDataIOS() ?? ""
|
|
259
272
|
}
|
|
260
273
|
|
|
261
274
|
AsyncFunction("requestReceiptRefreshIOS") { () async throws -> String in
|
|
275
|
+
try await ensureConnection()
|
|
262
276
|
logDebug("requestReceiptRefreshIOS called")
|
|
263
277
|
// Receipt refresh is handled automatically by StoreKit 2
|
|
264
278
|
return try await OpenIapModule.shared.getReceiptDataIOS() ?? ""
|
|
265
279
|
}
|
|
266
280
|
|
|
267
281
|
AsyncFunction("validateReceiptIOS") { (sku: String) async throws -> [String: Any?] in
|
|
282
|
+
try await ensureConnection()
|
|
268
283
|
logDebug("validateReceiptIOS called for sku: \(sku)")
|
|
269
284
|
do {
|
|
270
285
|
// Use OpenIapReceiptValidationProps to keep naming parity with OpenIAP
|
|
@@ -286,12 +301,14 @@ public class ExpoIapModule: Module {
|
|
|
286
301
|
// MARK: - iOS Specific Features
|
|
287
302
|
|
|
288
303
|
AsyncFunction("presentCodeRedemptionSheetIOS") { () async throws -> Bool in
|
|
304
|
+
try await ensureConnection()
|
|
289
305
|
logDebug("presentCodeRedemptionSheetIOS called")
|
|
290
306
|
let _ = try await OpenIapModule.shared.presentCodeRedemptionSheetIOS()
|
|
291
307
|
return true
|
|
292
308
|
}
|
|
293
309
|
|
|
294
310
|
AsyncFunction("showManageSubscriptionsIOS") { () async throws -> Bool in
|
|
311
|
+
try await ensureConnection()
|
|
295
312
|
logDebug("showManageSubscriptionsIOS called")
|
|
296
313
|
let _ = try await OpenIapModule.shared.showManageSubscriptionsIOS()
|
|
297
314
|
return true
|
|
@@ -310,11 +327,13 @@ public class ExpoIapModule: Module {
|
|
|
310
327
|
}
|
|
311
328
|
|
|
312
329
|
AsyncFunction("beginRefundRequestIOS") { (sku: String) async throws -> String? in
|
|
330
|
+
try await ensureConnection()
|
|
313
331
|
logDebug("beginRefundRequestIOS called for sku: \(sku)")
|
|
314
332
|
return try await OpenIapModule.shared.beginRefundRequestIOS(sku: sku)
|
|
315
333
|
}
|
|
316
334
|
|
|
317
335
|
AsyncFunction("getPromotedProductIOS") { () async throws -> [String: Any?]? in
|
|
336
|
+
try await ensureConnection()
|
|
318
337
|
logDebug("getPromotedProductIOS called")
|
|
319
338
|
|
|
320
339
|
if let promoted = try await OpenIapModule.shared.getPromotedProductIOS() {
|
|
@@ -327,11 +346,13 @@ public class ExpoIapModule: Module {
|
|
|
327
346
|
return nil
|
|
328
347
|
}
|
|
329
348
|
AsyncFunction("getStorefrontIOS") { () async throws -> String in
|
|
349
|
+
try await ensureConnection()
|
|
330
350
|
logDebug("getStorefrontIOS called")
|
|
331
351
|
return try await OpenIapModule.shared.getStorefrontIOS()
|
|
332
352
|
}
|
|
333
353
|
|
|
334
354
|
AsyncFunction("syncIOS") { () async throws -> Bool in
|
|
355
|
+
try await ensureConnection()
|
|
335
356
|
logDebug("syncIOS called")
|
|
336
357
|
return try await OpenIapModule.shared.syncIOS()
|
|
337
358
|
}
|
|
@@ -339,21 +360,25 @@ public class ExpoIapModule: Module {
|
|
|
339
360
|
// MARK: - Additional iOS Methods
|
|
340
361
|
|
|
341
362
|
AsyncFunction("isTransactionVerifiedIOS") { (sku: String) async throws -> Bool in
|
|
363
|
+
try await ensureConnection()
|
|
342
364
|
logDebug("isTransactionVerifiedIOS called for sku: \(sku)")
|
|
343
365
|
return await OpenIapModule.shared.isTransactionVerifiedIOS(sku: sku)
|
|
344
366
|
}
|
|
345
367
|
|
|
346
368
|
AsyncFunction("getTransactionJwsIOS") { (sku: String) async throws -> String? in
|
|
369
|
+
try await ensureConnection()
|
|
347
370
|
logDebug("getTransactionJwsIOS called for sku: \(sku)")
|
|
348
371
|
return try await OpenIapModule.shared.getTransactionJwsIOS(sku: sku)
|
|
349
372
|
}
|
|
350
373
|
|
|
351
374
|
AsyncFunction("isEligibleForIntroOfferIOS") { (groupID: String) async throws -> Bool in
|
|
375
|
+
try await ensureConnection()
|
|
352
376
|
logDebug("isEligibleForIntroOfferIOS called for groupID: \(groupID)")
|
|
353
377
|
return await OpenIapModule.shared.isEligibleForIntroOfferIOS(groupID: groupID)
|
|
354
378
|
}
|
|
355
379
|
|
|
356
380
|
AsyncFunction("subscriptionStatusIOS") { (sku: String) async throws -> [[String: Any?]]? in
|
|
381
|
+
try await ensureConnection()
|
|
357
382
|
logDebug("subscriptionStatusIOS called for sku: \(sku)")
|
|
358
383
|
|
|
359
384
|
if let statuses = try await OpenIapModule.shared.subscriptionStatusIOS(sku: sku) {
|
|
@@ -365,27 +390,9 @@ public class ExpoIapModule: Module {
|
|
|
365
390
|
]
|
|
366
391
|
|
|
367
392
|
if let info = status.renewalInfo {
|
|
368
|
-
//
|
|
369
|
-
let willAutoRenew: Bool = {
|
|
370
|
-
// Try boolean first
|
|
371
|
-
if let b = info.autoRenewStatus as? Bool { return b }
|
|
372
|
-
// Fallback to string normalization
|
|
373
|
-
let normalized = String(describing: info.autoRenewStatus).lowercased()
|
|
374
|
-
let truthy = Set([
|
|
375
|
-
"willrenew",
|
|
376
|
-
"will_autorenew",
|
|
377
|
-
"will-auto-renew",
|
|
378
|
-
"auto_renew_on",
|
|
379
|
-
"true",
|
|
380
|
-
"1",
|
|
381
|
-
"on",
|
|
382
|
-
"yes",
|
|
383
|
-
])
|
|
384
|
-
return truthy.contains(normalized)
|
|
385
|
-
}()
|
|
386
|
-
|
|
393
|
+
// autoRenewStatus is a Bool from OpenIAP types
|
|
387
394
|
let renewalInfo: [String: Any?] = [
|
|
388
|
-
"willAutoRenew":
|
|
395
|
+
"willAutoRenew": info.autoRenewStatus,
|
|
389
396
|
"autoRenewPreference": info.autoRenewPreference
|
|
390
397
|
]
|
|
391
398
|
dict["renewalInfo"] = renewalInfo
|
|
@@ -398,6 +405,7 @@ public class ExpoIapModule: Module {
|
|
|
398
405
|
}
|
|
399
406
|
|
|
400
407
|
AsyncFunction("currentEntitlementIOS") { (sku: String) async throws -> [String: Any?]? in
|
|
408
|
+
try await ensureConnection()
|
|
401
409
|
logDebug("currentEntitlementIOS called for sku: \(sku)")
|
|
402
410
|
do {
|
|
403
411
|
if let entitlement = try await OpenIapModule.shared.currentEntitlementIOS(sku: sku) {
|
|
@@ -410,6 +418,7 @@ public class ExpoIapModule: Module {
|
|
|
410
418
|
}
|
|
411
419
|
|
|
412
420
|
AsyncFunction("latestTransactionIOS") { (sku: String) async throws -> [String: Any?]? in
|
|
421
|
+
try await ensureConnection()
|
|
413
422
|
logDebug("latestTransactionIOS called for sku: \(sku)")
|
|
414
423
|
do {
|
|
415
424
|
if let transaction = try await OpenIapModule.shared.latestTransactionIOS(sku: sku) {
|
|
@@ -468,4 +477,15 @@ public class ExpoIapModule: Module {
|
|
|
468
477
|
_ = try? await OpenIapModule.shared.endConnection()
|
|
469
478
|
}
|
|
470
479
|
|
|
480
|
+
// MARK: - Private Helper Methods
|
|
481
|
+
|
|
482
|
+
private func ensureConnection() throws {
|
|
483
|
+
guard isInitialized else {
|
|
484
|
+
throw OpenIapError.make(
|
|
485
|
+
code: OpenIapError.E_INIT_CONNECTION,
|
|
486
|
+
message: "Connection not initialized. Call initConnection() first."
|
|
487
|
+
)
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
471
491
|
}
|
package/package.json
CHANGED