expo-iap 3.1.35 → 3.1.36

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 CHANGED
@@ -93,10 +93,9 @@ The library follows the OpenIAP type specifications with platform-specific exten
93
93
  ### Hook API Semantics (useIAP)
94
94
 
95
95
  - Inside the `useIAP` hook, most methods return `Promise<void>` and update internal state. Do not design examples or implementations that expect data from these methods.
96
- - Examples: `fetchProducts`, `requestPurchase`, `getAvailablePurchases`.
97
- - After calling, consume state from the hook: `products`, `subscriptions`, `availablePurchases`, etc.
96
+ - Examples: `fetchProducts`, `requestPurchase`, `getAvailablePurchases`, `getActiveSubscriptions`.
97
+ - After calling, consume state from the hook: `products`, `subscriptions`, `availablePurchases`, `activeSubscriptions`, etc.
98
98
  - Defined exceptions that DO return values in the hook:
99
- - `getActiveSubscriptions(subscriptionIds?) => Promise<ActiveSubscription[]>` (also updates `activeSubscriptions` state)
100
99
  - `hasActiveSubscriptions(subscriptionIds?) => Promise<boolean>`
101
100
  - The root (index) API is value-returning and can be awaited to receive data directly. Use root API when not using React state.
102
101
 
@@ -307,12 +307,19 @@ class ExpoIapModule : Module() {
307
307
  ExpoIapHelper.resolvePurchasePromises(purchases.map { it.toJson() })
308
308
  } catch (e: Exception) {
309
309
  ExpoIapLog.failure("requestPurchaseAndroid", e)
310
+ // Try to use toJSON() if available (OpenIAP PurchaseError), otherwise create a generic error map
310
311
  val errorMap =
311
- mapOf(
312
- "code" to OpenIapError.PurchaseFailed.CODE,
313
- "message" to (e.message ?: "Purchase failed"),
314
- "platform" to "android",
315
- )
312
+ runCatching {
313
+ @Suppress("UNCHECKED_CAST")
314
+ e.javaClass.getMethod("toJSON").invoke(e) as Map<String, Any?>
315
+ }.getOrElse {
316
+ mapOf(
317
+ "code" to OpenIapError.PurchaseFailed.CODE,
318
+ "message" to (e.message ?: "Purchase failed"),
319
+ "platform" to "android",
320
+ )
321
+ }
322
+ val errorCode = errorMap["code"] as? String ?: OpenIapError.PurchaseFailed.CODE
316
323
  runCatching {
317
324
  ExpoIapHelper.emitOrQueue(
318
325
  this@ExpoIapModule,
@@ -330,7 +337,7 @@ class ExpoIapModule : Module() {
330
337
  )
331
338
  }
332
339
  ExpoIapHelper.rejectPurchasePromises(
333
- OpenIapError.PurchaseFailed.CODE,
340
+ errorCode,
334
341
  e.message,
335
342
  null,
336
343
  )
@@ -1,6 +1,6 @@
1
1
  <?xml version="1.0" encoding="UTF-8"?>
2
- <coverage generated="1763466722728" clover="3.2.0">
3
- <project timestamp="1763466722728" name="All files">
2
+ <coverage generated="1764088678733" clover="3.2.0">
3
+ <project timestamp="1764088678733" name="All files">
4
4
  <metrics statements="457" coveredstatements="429" conditionals="251" coveredconditionals="217" methods="95" coveredmethods="75" elements="803" coveredelements="721" complexity="0" loc="457" ncloc="457" packages="3" files="5" classes="5"/>
5
5
  <package name="src">
6
6
  <metrics statements="196" coveredstatements="190" conditionals="99" coveredconditionals="89" methods="41" coveredmethods="32"/>
@@ -131,7 +131,7 @@
131
131
  <div class='footer quiet pad2 space-top1 center small'>
132
132
  Code coverage generated by
133
133
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
134
- at 2025-11-18T11:52:02.710Z
134
+ at 2025-11-25T16:37:58.715Z
135
135
  </div>
136
136
  <script src="prettify.js"></script>
137
137
  <script>
@@ -101,7 +101,7 @@
101
101
  <div class='footer quiet pad2 space-top1 center small'>
102
102
  Code coverage generated by
103
103
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
104
- at 2025-11-18T11:52:02.710Z
104
+ at 2025-11-25T16:37:58.715Z
105
105
  </div>
106
106
  <script src="../prettify.js"></script>
107
107
  <script>
@@ -2335,7 +2335,7 @@ export {<span class="fstat-no" title="function not covered" >ExpoIapConsole}</sp
2335
2335
  <div class='footer quiet pad2 space-top1 center small'>
2336
2336
  Code coverage generated by
2337
2337
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
2338
- at 2025-11-18T11:52:02.710Z
2338
+ at 2025-11-25T16:37:58.715Z
2339
2339
  </div>
2340
2340
  <script src="../prettify.js"></script>
2341
2341
  <script>
@@ -814,7 +814,7 @@ export const createAlternativeBillingTokenAndroid: MutationField&lt;
814
814
  <div class='footer quiet pad2 space-top1 center small'>
815
815
  Code coverage generated by
816
816
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
817
- at 2025-11-18T11:52:02.710Z
817
+ at 2025-11-25T16:37:58.715Z
818
818
  </div>
819
819
  <script src="../../prettify.js"></script>
820
820
  <script>
@@ -116,7 +116,7 @@
116
116
  <div class='footer quiet pad2 space-top1 center small'>
117
117
  Code coverage generated by
118
118
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
119
- at 2025-11-18T11:52:02.710Z
119
+ at 2025-11-25T16:37:58.715Z
120
120
  </div>
121
121
  <script src="../../prettify.js"></script>
122
122
  <script>
@@ -1267,7 +1267,7 @@ export const presentExternalPurchaseLinkIOS: MutationField&lt;
1267
1267
  <div class='footer quiet pad2 space-top1 center small'>
1268
1268
  Code coverage generated by
1269
1269
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
1270
- at 2025-11-18T11:52:02.710Z
1270
+ at 2025-11-25T16:37:58.715Z
1271
1271
  </div>
1272
1272
  <script src="../../prettify.js"></script>
1273
1273
  <script>
@@ -268,7 +268,7 @@ export const ExpoIapConsole = createConsole();
268
268
  <div class='footer quiet pad2 space-top1 center small'>
269
269
  Code coverage generated by
270
270
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
271
- at 2025-11-18T11:52:02.710Z
271
+ at 2025-11-25T16:37:58.715Z
272
272
  </div>
273
273
  <script src="../../prettify.js"></script>
274
274
  <script>
@@ -1111,7 +1111,7 @@ export function getUserFriendlyErrorMessage(error: ErrorLike): string {
1111
1111
  <div class='footer quiet pad2 space-top1 center small'>
1112
1112
  Code coverage generated by
1113
1113
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
1114
- at 2025-11-18T11:52:02.710Z
1114
+ at 2025-11-25T16:37:58.715Z
1115
1115
  </div>
1116
1116
  <script src="../../prettify.js"></script>
1117
1117
  <script>
@@ -116,7 +116,7 @@
116
116
  <div class='footer quiet pad2 space-top1 center small'>
117
117
  Code coverage generated by
118
118
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
119
- at 2025-11-18T11:52:02.710Z
119
+ at 2025-11-25T16:37:58.715Z
120
120
  </div>
121
121
  <script src="../../prettify.js"></script>
122
122
  <script>
@@ -1,6 +1,24 @@
1
+ import ExpoModulesCore
1
2
  import Foundation
2
3
  import OpenIAP
3
4
 
5
+ /// Exception wrapper for PurchaseError that preserves OpenIAP error codes
6
+ /// This ensures consistent error format between try-catch and onPurchaseError callback
7
+ class IapException: GenericException<(code: String, message: String, productId: String?)> {
8
+ override var code: String { param.code }
9
+ override var reason: String { param.message }
10
+
11
+ var productId: String? { param.productId }
12
+
13
+ static func from(_ error: PurchaseError) -> IapException {
14
+ let payload = OpenIapSerialization.encode(error)
15
+ let code = payload["code"] as? String ?? "unknown"
16
+ let message = payload["message"] as? String ?? error.localizedDescription
17
+ let productId = payload["productId"] as? String
18
+ return IapException((code: code, message: message, productId: productId))
19
+ }
20
+ }
21
+
4
22
  enum ExpoIapHelper {
5
23
  private static var listeners: [Subscription] = []
6
24
 
@@ -86,10 +86,11 @@ public final class ExpoIapModule: Module {
86
86
  }
87
87
  } catch let error as PurchaseError {
88
88
  ExpoIapLog.failure("requestPurchase", error: error)
89
- throw error
89
+ throw IapException.from(error)
90
90
  } catch {
91
91
  ExpoIapLog.failure("requestPurchase", error: error)
92
- throw PurchaseError.make(code: .purchaseError, message: error.localizedDescription)
92
+ throw IapException.from(
93
+ PurchaseError.make(code: .purchaseError, message: error.localizedDescription))
93
94
  }
94
95
  }
95
96
 
@@ -198,10 +199,10 @@ public final class ExpoIapModule: Module {
198
199
  return sanitized
199
200
  } catch let error as PurchaseError {
200
201
  ExpoIapLog.failure("validateReceiptIOS", error: error)
201
- throw error
202
+ throw IapException.from(error)
202
203
  } catch {
203
204
  ExpoIapLog.failure("validateReceiptIOS", error: error)
204
- throw PurchaseError.make(code: .receiptFailed)
205
+ throw IapException.from(PurchaseError.make(code: .receiptFailed))
205
206
  }
206
207
  }
207
208
 
@@ -312,10 +313,10 @@ public final class ExpoIapModule: Module {
312
313
  return nil
313
314
  } catch let error as PurchaseError {
314
315
  ExpoIapLog.failure("currentEntitlementIOS", error: error)
315
- throw error
316
+ throw IapException.from(error)
316
317
  } catch {
317
318
  ExpoIapLog.failure("currentEntitlementIOS", error: error)
318
- throw PurchaseError.make(code: .skuNotFound, productId: sku)
319
+ throw IapException.from(PurchaseError.make(code: .skuNotFound, productId: sku))
319
320
  }
320
321
  }
321
322
 
@@ -332,10 +333,10 @@ public final class ExpoIapModule: Module {
332
333
  return nil
333
334
  } catch let error as PurchaseError {
334
335
  ExpoIapLog.failure("latestTransactionIOS", error: error)
335
- throw error
336
+ throw IapException.from(error)
336
337
  } catch {
337
338
  ExpoIapLog.failure("latestTransactionIOS", error: error)
338
- throw PurchaseError.make(code: .skuNotFound, productId: sku)
339
+ throw IapException.from(PurchaseError.make(code: .skuNotFound, productId: sku))
339
340
  }
340
341
  }
341
342
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-iap",
3
- "version": "3.1.35",
3
+ "version": "3.1.36",
4
4
  "description": "In App Purchase module in Expo",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",