expo-iap 3.0.3 → 3.0.5

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.
Files changed (70) hide show
  1. package/.eslintignore +1 -1
  2. package/.eslintrc.js +1 -0
  3. package/.prettierignore +1 -0
  4. package/CHANGELOG.md +13 -0
  5. package/CLAUDE.md +2 -0
  6. package/CONTRIBUTING.md +10 -0
  7. package/android/build.gradle +1 -1
  8. package/android/src/main/java/expo/modules/iap/ExpoIapModule.kt +12 -12
  9. package/android/src/main/java/expo/modules/iap/PromiseUtils.kt +2 -2
  10. package/build/helpers/subscription.d.ts +1 -12
  11. package/build/helpers/subscription.d.ts.map +1 -1
  12. package/build/helpers/subscription.js +12 -7
  13. package/build/helpers/subscription.js.map +1 -1
  14. package/build/index.d.ts +34 -18
  15. package/build/index.d.ts.map +1 -1
  16. package/build/index.js +40 -17
  17. package/build/index.js.map +1 -1
  18. package/build/modules/android.d.ts +5 -5
  19. package/build/modules/android.d.ts.map +1 -1
  20. package/build/modules/android.js +17 -4
  21. package/build/modules/android.js.map +1 -1
  22. package/build/modules/ios.d.ts +4 -8
  23. package/build/modules/ios.d.ts.map +1 -1
  24. package/build/modules/ios.js.map +1 -1
  25. package/build/purchase-error.d.ts +67 -0
  26. package/build/purchase-error.d.ts.map +1 -0
  27. package/build/purchase-error.js +166 -0
  28. package/build/purchase-error.js.map +1 -0
  29. package/build/types.d.ts +604 -0
  30. package/build/types.d.ts.map +1 -0
  31. package/build/types.js +42 -0
  32. package/build/types.js.map +1 -0
  33. package/build/useIAP.d.ts +8 -10
  34. package/build/useIAP.d.ts.map +1 -1
  35. package/build/useIAP.js +1 -1
  36. package/build/useIAP.js.map +1 -1
  37. package/build/utils/errorMapping.d.ts +1 -1
  38. package/build/utils/errorMapping.d.ts.map +1 -1
  39. package/build/utils/errorMapping.js +19 -3
  40. package/build/utils/errorMapping.js.map +1 -1
  41. package/ios/ExpoIap.podspec +1 -1
  42. package/ios/ExpoIapModule.swift +103 -89
  43. package/jest.config.js +1 -1
  44. package/package.json +2 -1
  45. package/plugin/build/withIAP.js +4 -5
  46. package/plugin/src/withIAP.ts +4 -5
  47. package/scripts/update-types.mjs +61 -0
  48. package/src/helpers/subscription.ts +12 -20
  49. package/src/index.ts +89 -41
  50. package/src/modules/android.ts +24 -8
  51. package/src/modules/ios.ts +7 -11
  52. package/src/purchase-error.ts +265 -0
  53. package/src/types.ts +705 -0
  54. package/src/useIAP.ts +16 -16
  55. package/src/utils/errorMapping.ts +24 -3
  56. package/build/ExpoIap.types.d.ts +0 -307
  57. package/build/ExpoIap.types.d.ts.map +0 -1
  58. package/build/ExpoIap.types.js +0 -235
  59. package/build/ExpoIap.types.js.map +0 -1
  60. package/build/types/ExpoIapAndroid.types.d.ts +0 -114
  61. package/build/types/ExpoIapAndroid.types.d.ts.map +0 -1
  62. package/build/types/ExpoIapAndroid.types.js +0 -29
  63. package/build/types/ExpoIapAndroid.types.js.map +0 -1
  64. package/build/types/ExpoIapIOS.types.d.ts +0 -149
  65. package/build/types/ExpoIapIOS.types.d.ts.map +0 -1
  66. package/build/types/ExpoIapIOS.types.js +0 -8
  67. package/build/types/ExpoIapIOS.types.js.map +0 -1
  68. package/src/ExpoIap.types.ts +0 -444
  69. package/src/types/ExpoIapAndroid.types.ts +0 -133
  70. package/src/types/ExpoIapIOS.types.ts +0 -172
package/.eslintignore CHANGED
@@ -2,4 +2,4 @@ docs/
2
2
  example/
3
3
  build/
4
4
  node_modules/
5
- *.d.ts
5
+ *.d.ts
package/.eslintrc.js CHANGED
@@ -1,6 +1,7 @@
1
1
  module.exports = {
2
2
  root: true,
3
3
  extends: ['expo', 'prettier'],
4
+ ignorePatterns: ['src/types.ts'],
4
5
  plugins: ['prettier'],
5
6
  rules: {
6
7
  'eslint-comments/no-unlimited-disable': 0,
@@ -0,0 +1 @@
1
+ src/types.ts
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 3.0.5 - 2025-09-17
4
+
5
+ - Types: Normalize OpenIAP literal unions to `'in-app'`, `'ios'`, and `'android'`, update `useIAP` helpers to the new `PurchaseRequestInput`, and refresh docs/examples/tests to the lowercase schema tokens.
6
+ - Tooling: Regenerate `src/types.ts` with `openiap-gql` 1.0.2, add lint/format ignores for the generated file, and document the type update workflow.
7
+ - Native: Upgrade the Android fallback/config plugin to [openiap-google 1.1.10](https://github.com/hyodotdev/openiap-google/releases/tag/1.1.10), bump the iOS pod to [openiap 1.1.12](https://github.com/hyodotdev/openiap-apple/releases/tag/1.1.12), adopt PascalCase error codes, and wire the new request parameter models through the bridges and config plugin.
8
+
9
+ ## 3.0.4 - 2025-09-16
10
+
11
+ - Types: Regenerate the OpenIAP schema with the canonical PascalCase names (`ProductIOS`, `PurchaseIOS`, etc.) and align docs/tests/examples with the new exports.
12
+ - Errors: Promote `PurchaseError` to extend `Error`, tighten typings for platform error input/output, and ensure Android acknowledgement resolves a `VoidResult {success}` object.
13
+ - Docs: Refresh iOS setup examples to use platform-specific request shapes, fix legacy `ErrorCode` references in versioned guides, and trim example helpers to the updated API surface.
14
+ - Build: Adopt [openiap-gql 1.0.0](https://github.com/hyodotdev/openiap-gql/releases/tag/1.0.0) for the transport layer to stay aligned with the GraphQL contract shipped across the ecosystem.
15
+
3
16
  ## 3.0.3 - 2025-09-14
4
17
 
5
18
  - Types: Align Expo IAP surface with [react-native-iap #3006](https://github.com/hyochan/react-native-iap/pull/3006) by renaming subscription aliases, adding StoreKit product enums, and exposing optional purchase metadata (quantity, purchaseState, isAutoRenewing).
package/CLAUDE.md CHANGED
@@ -62,6 +62,8 @@ For complete type definitions and documentation, see: <https://www.openiap.dev/d
62
62
 
63
63
  The library follows the OpenIAP type specifications with platform-specific extensions using iOS/Android suffixes.
64
64
 
65
+ > **Important:** `src/types.ts` is generated from the OpenIAP schema. Never edit this file manually or commit hand-written changes. After updating any `*.graphql` schema, run `bun run generate:types` (or the equivalent script in your package manager) to refresh the file.
66
+
65
67
  ### React/JSX Conventions
66
68
 
67
69
  - **Conditional Rendering**: Use ternary operator with null instead of logical AND
package/CONTRIBUTING.md CHANGED
@@ -213,6 +213,16 @@ For detailed code conventions, naming standards, and implementation guidelines,
213
213
  - Pre-commit checks
214
214
  - OpenIAP specification compliance
215
215
 
216
+ ### Updating OpenIAP Types
217
+
218
+ The generated TypeScript definitions in `src/types.ts` come from the [`openiap-gql`](https://github.com/hyodotdev/openiap-gql) release artifacts. Never edit this file by hand. When the schema changes or you need to pull newer types:
219
+
220
+ - Run `bun run generate:types` to download the latest pinned release and overwrite `src/types.ts`.
221
+ - To target a specific release, pass the tag: `bun run generate:types --tag <version>`.
222
+ - Commit the updated file alongside any related schema or documentation changes.
223
+
224
+ Always ensure the repository builds and tests succeed after regenerating the types.
225
+
216
226
  ### Development Workflow
217
227
 
218
228
  1. **Before starting work**:
@@ -58,6 +58,6 @@ dependencies {
58
58
  implementation project(":openiap-google")
59
59
  } else {
60
60
  // Fallback to published artifact when local project isn't linked
61
- implementation "io.github.hyochan.openiap:openiap-google:1.1.0"
61
+ implementation "io.github.hyochan.openiap:openiap-google:1.1.10"
62
62
  }
63
63
  }
@@ -6,7 +6,7 @@ import dev.hyo.openiap.OpenIapError
6
6
  import dev.hyo.openiap.OpenIapModule
7
7
  import dev.hyo.openiap.models.DeepLinkOptions
8
8
  import dev.hyo.openiap.models.ProductRequest
9
- import dev.hyo.openiap.models.RequestPurchaseAndroidProps
9
+ import dev.hyo.openiap.models.RequestPurchaseParams
10
10
  import expo.modules.kotlin.Promise
11
11
  import expo.modules.kotlin.exception.Exceptions
12
12
  import expo.modules.kotlin.modules.Module
@@ -103,7 +103,7 @@ class ExpoIapModule : Module() {
103
103
  if (!ok) {
104
104
  // Clear any buffered events from a failed init
105
105
  pendingEvents.clear()
106
- promise.reject(OpenIapError.E_INIT_CONNECTION, "Failed to initialize connection", null)
106
+ promise.reject(OpenIapError.InitConnection.CODE, "Failed to initialize connection", null)
107
107
  return@withLock
108
108
  }
109
109
 
@@ -118,7 +118,7 @@ class ExpoIapModule : Module() {
118
118
 
119
119
  promise.resolve(true)
120
120
  } catch (e: Exception) {
121
- promise.reject(OpenIapError.E_INIT_CONNECTION, e.message, e)
121
+ promise.reject(OpenIapError.InitConnection.CODE, e.message, e)
122
122
  }
123
123
  }
124
124
  }
@@ -143,7 +143,7 @@ class ExpoIapModule : Module() {
143
143
  val products = openIap.fetchProducts(ProductRequest(skuArr.toList(), reqType))
144
144
  promise.resolve(products.map { it.toJSON() })
145
145
  } catch (e: Exception) {
146
- promise.reject(OpenIapError.E_QUERY_PRODUCT, e.message, null)
146
+ promise.reject(OpenIapError.QueryProduct.CODE, e.message, null)
147
147
  }
148
148
  }
149
149
  }
@@ -154,7 +154,7 @@ class ExpoIapModule : Module() {
154
154
  val purchases = openIap.getAvailablePurchases(null)
155
155
  promise.resolve(purchases.map { it.toJSON() })
156
156
  } catch (e: Exception) {
157
- promise.reject(OpenIapError.E_SERVICE_ERROR, e.message, null)
157
+ promise.reject(OpenIapError.ServiceUnavailable.CODE, e.message, null)
158
158
  }
159
159
  }
160
160
  }
@@ -168,7 +168,7 @@ class ExpoIapModule : Module() {
168
168
  openIap.deepLinkToSubscriptions(DeepLinkOptions(sku, packageName))
169
169
  promise.resolve(null)
170
170
  } catch (e: Exception) {
171
- promise.reject(OpenIapError.E_SERVICE_ERROR, e.message, null)
171
+ promise.reject(OpenIapError.ServiceUnavailable.CODE, e.message, null)
172
172
  }
173
173
  }
174
174
  }
@@ -180,7 +180,7 @@ class ExpoIapModule : Module() {
180
180
  val code = openIap.getStorefront()
181
181
  promise.resolve(code)
182
182
  } catch (e: Exception) {
183
- promise.reject(OpenIapError.E_SERVICE_ERROR, e.message, e)
183
+ promise.reject(OpenIapError.ServiceUnavailable.CODE, e.message, e)
184
184
  }
185
185
  }
186
186
  }
@@ -204,7 +204,7 @@ class ExpoIapModule : Module() {
204
204
  val reqType = ProductRequest.ProductRequestType.fromString(type)
205
205
  val result =
206
206
  openIap.requestPurchase(
207
- RequestPurchaseAndroidProps(
207
+ RequestPurchaseParams(
208
208
  skus = skus,
209
209
  obfuscatedAccountIdAndroid = obfuscatedAccountId,
210
210
  obfuscatedProfileIdAndroid = obfuscatedProfileId,
@@ -223,7 +223,7 @@ class ExpoIapModule : Module() {
223
223
  } catch (e: Exception) {
224
224
  val errorMap =
225
225
  mapOf(
226
- "code" to OpenIapError.E_PURCHASE_ERROR,
226
+ "code" to OpenIapError.PurchaseFailed.CODE,
227
227
  "message" to (e.message ?: "Purchase failed"),
228
228
  "platform" to "android",
229
229
  )
@@ -235,7 +235,7 @@ class ExpoIapModule : Module() {
235
235
  // Reject and clear any pending promises for this purchase flow
236
236
  PromiseUtils.rejectPromisesForKey(
237
237
  PromiseUtils.PROMISE_BUY_ITEM,
238
- OpenIapError.E_PURCHASE_ERROR,
238
+ OpenIapError.PurchaseFailed.CODE,
239
239
  e.message,
240
240
  null,
241
241
  )
@@ -249,7 +249,7 @@ class ExpoIapModule : Module() {
249
249
  openIap.acknowledgePurchaseAndroid(token)
250
250
  promise.resolve(mapOf("responseCode" to 0))
251
251
  } catch (e: Exception) {
252
- promise.reject(OpenIapError.E_SERVICE_ERROR, e.message, null)
252
+ promise.reject(OpenIapError.ServiceUnavailable.CODE, e.message, null)
253
253
  }
254
254
  }
255
255
  }
@@ -261,7 +261,7 @@ class ExpoIapModule : Module() {
261
261
  openIap.consumePurchaseAndroid(token)
262
262
  promise.resolve(mapOf("responseCode" to 0, "purchaseToken" to token))
263
263
  } catch (e: Exception) {
264
- promise.reject(OpenIapError.E_SERVICE_ERROR, e.message, null)
264
+ promise.reject(OpenIapError.ServiceUnavailable.CODE, e.message, null)
265
265
  }
266
266
  }
267
267
  }
@@ -32,7 +32,7 @@ object PromiseUtils {
32
32
  fun rejectAllPendingPromises() {
33
33
  // Snapshot to avoid concurrent modification
34
34
  promises.values.flatMap { it.toList() }.forEach { promise ->
35
- promise.safeReject(OpenIapError.E_CONNECTION_CLOSED, "Connection has been closed", null)
35
+ promise.safeReject(OpenIapError.ServiceDisconnected.CODE, "Connection has been closed", null)
36
36
  }
37
37
  promises.clear()
38
38
  }
@@ -60,7 +60,7 @@ fun Promise.safeResolve(value: Any?) {
60
60
  }
61
61
  }
62
62
 
63
- fun Promise.safeReject(message: String) = this.safeReject("E_UNKNOWN", message, null)
63
+ fun Promise.safeReject(message: String) = this.safeReject(OpenIapError.UnknownError.CODE, message, null)
64
64
 
65
65
  fun Promise.safeReject(
66
66
  code: String,
@@ -1,15 +1,4 @@
1
- export interface ActiveSubscription {
2
- productId: string;
3
- isActive: boolean;
4
- transactionId: string;
5
- purchaseToken?: string;
6
- transactionDate: number;
7
- expirationDateIOS?: Date;
8
- autoRenewingAndroid?: boolean;
9
- environmentIOS?: string;
10
- willExpireSoon?: boolean;
11
- daysUntilExpirationIOS?: number;
12
- }
1
+ import type { ActiveSubscription } from '../types';
13
2
  /**
14
3
  * Get all active subscriptions with detailed information
15
4
  * @param subscriptionIds - Optional array of subscription product IDs to filter. If not provided, returns all active subscriptions.
@@ -1 +1 @@
1
- {"version":3,"file":"subscription.d.ts","sourceRoot":"","sources":["../../src/helpers/subscription.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,CAAC,EAAE,IAAI,CAAC;IACzB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,sBAAsB,CAAC,EAAE,MAAM,CAAC;CACjC;AAED;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,GACjC,kBAAkB,MAAM,EAAE,KACzB,OAAO,CAAC,kBAAkB,EAAE,CA6G9B,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,GACjC,kBAAkB,MAAM,EAAE,KACzB,OAAO,CAAC,OAAO,CAGjB,CAAC"}
1
+ {"version":3,"file":"subscription.d.ts","sourceRoot":"","sources":["../../src/helpers/subscription.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,UAAU,CAAC;AAEjD;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,GACjC,kBAAkB,MAAM,EAAE,KACzB,OAAO,CAAC,kBAAkB,EAAE,CAiH9B,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,GACjC,kBAAkB,MAAM,EAAE,KACzB,OAAO,CAAC,OAAO,CAGjB,CAAC"}
@@ -65,30 +65,35 @@ export const getActiveSubscriptions = async (subscriptionIds) => {
65
65
  const subscription = {
66
66
  productId: purchase.productId,
67
67
  isActive: true,
68
- // Use unified id as transaction identifier in v3
69
- transactionId: purchase.id,
68
+ transactionId: String(purchase.id),
70
69
  purchaseToken: purchase.purchaseToken,
71
70
  transactionDate: purchase.transactionDate,
72
71
  };
73
72
  // Add platform-specific details
74
73
  if (Platform.OS === 'ios') {
75
74
  if ('expirationDateIOS' in purchase && purchase.expirationDateIOS) {
76
- const expirationDate = new Date(purchase.expirationDateIOS);
77
- subscription.expirationDateIOS = expirationDate;
75
+ subscription.expirationDateIOS = purchase.expirationDateIOS;
78
76
  // Calculate days until expiration (round to nearest day)
79
77
  const daysUntilExpiration = Math.round((purchase.expirationDateIOS - currentTime) / (1000 * 60 * 60 * 24));
80
78
  subscription.daysUntilExpirationIOS = daysUntilExpiration;
81
79
  subscription.willExpireSoon = daysUntilExpiration <= 7;
82
80
  }
83
81
  if ('environmentIOS' in purchase) {
84
- subscription.environmentIOS = purchase.environmentIOS;
82
+ subscription.environmentIOS = purchase.environmentIOS ?? undefined;
85
83
  }
86
84
  }
87
85
  else if (Platform.OS === 'android') {
88
86
  if ('autoRenewingAndroid' in purchase) {
89
- subscription.autoRenewingAndroid = purchase.autoRenewingAndroid;
87
+ if (typeof purchase.autoRenewingAndroid !== 'undefined') {
88
+ subscription.autoRenewingAndroid = purchase.autoRenewingAndroid;
89
+ }
90
90
  // If auto-renewing is false, subscription will expire soon
91
- subscription.willExpireSoon = !purchase.autoRenewingAndroid;
91
+ if (purchase.autoRenewingAndroid === false) {
92
+ subscription.willExpireSoon = true;
93
+ }
94
+ else if (purchase.autoRenewingAndroid === true) {
95
+ subscription.willExpireSoon = false;
96
+ }
92
97
  }
93
98
  }
94
99
  activeSubscriptions.push(subscription);
@@ -1 +1 @@
1
- {"version":3,"file":"subscription.js","sourceRoot":"","sources":["../../src/helpers/subscription.ts"],"names":[],"mappings":"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","sourcesContent":["import {Platform} from 'react-native';\nimport {getAvailablePurchases} from '../index';\n\nexport interface ActiveSubscription {\n productId: string;\n isActive: boolean;\n transactionId: string; // Transaction identifier for backend validation\n purchaseToken?: string; // JWT token (iOS) or purchase token (Android) for backend validation\n transactionDate: number; // Transaction timestamp\n expirationDateIOS?: Date;\n autoRenewingAndroid?: boolean;\n environmentIOS?: string;\n willExpireSoon?: boolean;\n daysUntilExpirationIOS?: number;\n}\n\n/**\n * Get all active subscriptions with detailed information\n * @param subscriptionIds - Optional array of subscription product IDs to filter. If not provided, returns all active subscriptions.\n * @returns Promise<ActiveSubscription[]> array of active subscriptions with details\n */\nexport const getActiveSubscriptions = async (\n subscriptionIds?: string[],\n): Promise<ActiveSubscription[]> => {\n try {\n const purchases = await getAvailablePurchases();\n const currentTime = Date.now();\n const activeSubscriptions: ActiveSubscription[] = [];\n\n // Filter purchases to find active subscriptions\n const filteredPurchases = purchases.filter((purchase) => {\n // If specific IDs provided, filter by them\n if (subscriptionIds && subscriptionIds.length > 0) {\n if (!subscriptionIds.includes(purchase.productId)) {\n return false;\n }\n }\n\n // Check if this purchase has subscription-specific fields\n const hasSubscriptionFields =\n ('expirationDateIOS' in purchase && !!purchase.expirationDateIOS) ||\n 'autoRenewingAndroid' in purchase ||\n ('environmentIOS' in purchase && !!(purchase as any).environmentIOS);\n\n if (!hasSubscriptionFields) {\n return false;\n }\n\n // Check if it's actually active\n if (Platform.OS === 'ios') {\n if ('expirationDateIOS' in purchase && purchase.expirationDateIOS) {\n return purchase.expirationDateIOS > currentTime;\n }\n // For iOS purchases without expiration date (like Sandbox), we consider them active\n // if they have the environmentIOS field and were created recently\n if ('environmentIOS' in purchase && purchase.environmentIOS) {\n const dayInMs = 24 * 60 * 60 * 1000;\n // If no expiration date, consider active if transaction is recent (within 24 hours for Sandbox)\n if (\n !('expirationDateIOS' in purchase) ||\n !purchase.expirationDateIOS\n ) {\n if (\n purchase.environmentIOS === 'Sandbox' &&\n purchase.transactionDate &&\n currentTime - purchase.transactionDate < dayInMs\n ) {\n return true;\n }\n }\n }\n } else if (Platform.OS === 'android') {\n // For Android, if it's in the purchases list, it's active\n return true;\n }\n\n return false;\n });\n\n // Deduplicate by transaction identifier (id)\n const seen = new Set<string>();\n const dedupedPurchases = filteredPurchases.filter((p) => {\n const key = String(p.id);\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n\n // Convert to ActiveSubscription format\n for (const purchase of dedupedPurchases) {\n const subscription: ActiveSubscription = {\n productId: purchase.productId,\n isActive: true,\n // Use unified id as transaction identifier in v3\n transactionId: purchase.id,\n purchaseToken: purchase.purchaseToken,\n transactionDate: purchase.transactionDate,\n };\n\n // Add platform-specific details\n if (Platform.OS === 'ios') {\n if ('expirationDateIOS' in purchase && purchase.expirationDateIOS) {\n const expirationDate = new Date(purchase.expirationDateIOS);\n subscription.expirationDateIOS = expirationDate;\n\n // Calculate days until expiration (round to nearest day)\n const daysUntilExpiration = Math.round(\n (purchase.expirationDateIOS - currentTime) / (1000 * 60 * 60 * 24),\n );\n subscription.daysUntilExpirationIOS = daysUntilExpiration;\n subscription.willExpireSoon = daysUntilExpiration <= 7;\n }\n\n if ('environmentIOS' in purchase) {\n subscription.environmentIOS = purchase.environmentIOS;\n }\n } else if (Platform.OS === 'android') {\n if ('autoRenewingAndroid' in purchase) {\n subscription.autoRenewingAndroid = purchase.autoRenewingAndroid;\n // If auto-renewing is false, subscription will expire soon\n subscription.willExpireSoon = !purchase.autoRenewingAndroid;\n }\n }\n\n activeSubscriptions.push(subscription);\n }\n\n return activeSubscriptions;\n } catch (error) {\n console.error('Error getting active subscriptions:', error);\n return [];\n }\n};\n\n/**\n * Check if user has any active subscriptions\n * @param subscriptionIds - Optional array of subscription product IDs to check. If not provided, checks all subscriptions.\n * @returns Promise<boolean> true if user has at least one active subscription\n */\nexport const hasActiveSubscriptions = async (\n subscriptionIds?: string[],\n): Promise<boolean> => {\n const subscriptions = await getActiveSubscriptions(subscriptionIds);\n return subscriptions.length > 0;\n};\n"]}
1
+ {"version":3,"file":"subscription.js","sourceRoot":"","sources":["../../src/helpers/subscription.ts"],"names":[],"mappings":"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","sourcesContent":["import {Platform} from 'react-native';\nimport {getAvailablePurchases} from '../index';\nimport type {ActiveSubscription} from '../types';\n\n/**\n * Get all active subscriptions with detailed information\n * @param subscriptionIds - Optional array of subscription product IDs to filter. If not provided, returns all active subscriptions.\n * @returns Promise<ActiveSubscription[]> array of active subscriptions with details\n */\nexport const getActiveSubscriptions = async (\n subscriptionIds?: string[],\n): Promise<ActiveSubscription[]> => {\n try {\n const purchases = await getAvailablePurchases();\n const currentTime = Date.now();\n const activeSubscriptions: ActiveSubscription[] = [];\n\n // Filter purchases to find active subscriptions\n const filteredPurchases = purchases.filter((purchase) => {\n // If specific IDs provided, filter by them\n if (subscriptionIds && subscriptionIds.length > 0) {\n if (!subscriptionIds.includes(purchase.productId)) {\n return false;\n }\n }\n\n // Check if this purchase has subscription-specific fields\n const hasSubscriptionFields =\n ('expirationDateIOS' in purchase && !!purchase.expirationDateIOS) ||\n 'autoRenewingAndroid' in purchase ||\n ('environmentIOS' in purchase && !!(purchase as any).environmentIOS);\n\n if (!hasSubscriptionFields) {\n return false;\n }\n\n // Check if it's actually active\n if (Platform.OS === 'ios') {\n if ('expirationDateIOS' in purchase && purchase.expirationDateIOS) {\n return purchase.expirationDateIOS > currentTime;\n }\n // For iOS purchases without expiration date (like Sandbox), we consider them active\n // if they have the environmentIOS field and were created recently\n if ('environmentIOS' in purchase && purchase.environmentIOS) {\n const dayInMs = 24 * 60 * 60 * 1000;\n // If no expiration date, consider active if transaction is recent (within 24 hours for Sandbox)\n if (\n !('expirationDateIOS' in purchase) ||\n !purchase.expirationDateIOS\n ) {\n if (\n purchase.environmentIOS === 'Sandbox' &&\n purchase.transactionDate &&\n currentTime - purchase.transactionDate < dayInMs\n ) {\n return true;\n }\n }\n }\n } else if (Platform.OS === 'android') {\n // For Android, if it's in the purchases list, it's active\n return true;\n }\n\n return false;\n });\n\n // Deduplicate by transaction identifier (id)\n const seen = new Set<string>();\n const dedupedPurchases = filteredPurchases.filter((p) => {\n const key = String(p.id);\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n\n // Convert to ActiveSubscription format\n for (const purchase of dedupedPurchases) {\n const subscription: ActiveSubscription = {\n productId: purchase.productId,\n isActive: true,\n transactionId: String(purchase.id),\n purchaseToken: purchase.purchaseToken,\n transactionDate: purchase.transactionDate,\n };\n\n // Add platform-specific details\n if (Platform.OS === 'ios') {\n if ('expirationDateIOS' in purchase && purchase.expirationDateIOS) {\n subscription.expirationDateIOS = purchase.expirationDateIOS;\n\n // Calculate days until expiration (round to nearest day)\n const daysUntilExpiration = Math.round(\n (purchase.expirationDateIOS - currentTime) / (1000 * 60 * 60 * 24),\n );\n subscription.daysUntilExpirationIOS = daysUntilExpiration;\n subscription.willExpireSoon = daysUntilExpiration <= 7;\n }\n\n if ('environmentIOS' in purchase) {\n subscription.environmentIOS = purchase.environmentIOS ?? undefined;\n }\n } else if (Platform.OS === 'android') {\n if ('autoRenewingAndroid' in purchase) {\n if (typeof purchase.autoRenewingAndroid !== 'undefined') {\n subscription.autoRenewingAndroid = purchase.autoRenewingAndroid;\n }\n // If auto-renewing is false, subscription will expire soon\n if (purchase.autoRenewingAndroid === false) {\n subscription.willExpireSoon = true;\n } else if (purchase.autoRenewingAndroid === true) {\n subscription.willExpireSoon = false;\n }\n }\n }\n\n activeSubscriptions.push(subscription);\n }\n\n return activeSubscriptions;\n } catch (error) {\n console.error('Error getting active subscriptions:', error);\n return [];\n }\n};\n\n/**\n * Check if user has any active subscriptions\n * @param subscriptionIds - Optional array of subscription product IDs to check. If not provided, checks all subscriptions.\n * @returns Promise<boolean> true if user has at least one active subscription\n */\nexport const hasActiveSubscriptions = async (\n subscriptionIds?: string[],\n): Promise<boolean> => {\n const subscriptions = await getActiveSubscriptions(subscriptionIds);\n return subscriptions.length > 0;\n};\n"]}
package/build/index.d.ts CHANGED
@@ -1,8 +1,10 @@
1
- import { Product, Purchase, PurchaseError, PurchaseResult, RequestSubscriptionProps, RequestPurchaseProps, ProductSubscription } from './ExpoIap.types';
2
- export * from './ExpoIap.types';
1
+ import { Product, Purchase, RequestPurchaseProps, RequestPurchasePropsByPlatforms, RequestSubscriptionPropsByPlatforms, ProductSubscription, VoidResult, ReceiptValidationResult } from './types';
2
+ import { PurchaseError } from './purchase-error';
3
+ export * from './types';
4
+ export { ErrorCodeUtils, ErrorCodeMapping } from './purchase-error';
3
5
  export * from './modules/android';
4
6
  export * from './modules/ios';
5
- export { getActiveSubscriptions, hasActiveSubscriptions, type ActiveSubscription, } from './helpers/subscription';
7
+ export { getActiveSubscriptions, hasActiveSubscriptions, } from './helpers/subscription';
6
8
  export declare const PI: any;
7
9
  export declare enum OpenIapEvent {
8
10
  PurchaseUpdated = "purchase-updated",
@@ -16,6 +18,27 @@ export declare const emitter: {
16
18
  };
17
19
  removeListener: (eventName: string, listener: (...args: any[]) => void) => void;
18
20
  };
21
+ /**
22
+ * TODO(v3.1.0): Remove legacy 'inapp' alias once downstream apps migrate to 'in-app'.
23
+ */
24
+ export type ProductTypeInput = 'inapp' | 'in-app' | 'subs';
25
+ export type InAppTypeInput = Exclude<ProductTypeInput, 'subs'>;
26
+ type PurchaseRequestInApp = {
27
+ request: RequestPurchasePropsByPlatforms;
28
+ type?: InAppTypeInput;
29
+ };
30
+ type PurchaseRequestSubscription = {
31
+ request: RequestSubscriptionPropsByPlatforms;
32
+ type: 'subs';
33
+ };
34
+ export type PurchaseRequestInput = PurchaseRequestInApp | PurchaseRequestSubscription;
35
+ export type PurchaseRequest = {
36
+ request: RequestPurchaseProps;
37
+ type?: InAppTypeInput;
38
+ } | {
39
+ request: RequestSubscriptionPropsByPlatforms;
40
+ type: 'subs';
41
+ };
19
42
  export declare const purchaseUpdatedListener: (listener: (event: Purchase) => void) => {
20
43
  remove: () => void;
21
44
  };
@@ -52,14 +75,14 @@ export declare function endConnection(): Promise<boolean>;
52
75
  *
53
76
  * @param params - Product fetch configuration
54
77
  * @param params.skus - Array of product SKUs to fetch
55
- * @param params.type - Type of products: 'inapp' for regular products (default) or 'subs' for subscriptions
78
+ * @param params.type - Type of products: 'in-app' for regular products (default) or 'subs' for subscriptions
56
79
  *
57
80
  * @example
58
81
  * ```typescript
59
82
  * // Regular products
60
83
  * const products = await fetchProducts({
61
84
  * skus: ['product1', 'product2'],
62
- * type: 'inapp'
85
+ * type: 'in-app'
63
86
  * });
64
87
  *
65
88
  * // Subscriptions
@@ -71,7 +94,7 @@ export declare function endConnection(): Promise<boolean>;
71
94
  */
72
95
  export declare const fetchProducts: ({ skus, type, }: {
73
96
  skus: string[];
74
- type?: "inapp" | "subs";
97
+ type?: ProductTypeInput;
75
98
  }) => Promise<Product[] | ProductSubscription[]>;
76
99
  export declare const getAvailablePurchases: ({ alsoPublishToEventListenerIOS, onlyIncludeActiveItemsIOS, }?: {
77
100
  alsoPublishToEventListenerIOS?: boolean;
@@ -94,19 +117,12 @@ export declare const restorePurchases: (options?: {
94
117
  alsoPublishToEventListenerIOS?: boolean;
95
118
  onlyIncludeActiveItemsIOS?: boolean;
96
119
  }) => Promise<Purchase[]>;
97
- type PurchaseRequest = {
98
- request: RequestPurchaseProps;
99
- type?: 'inapp';
100
- } | {
101
- request: RequestSubscriptionProps;
102
- type: 'subs';
103
- };
104
120
  /**
105
121
  * Request a purchase for products or subscriptions.
106
122
  *
107
123
  * @param requestObj - Purchase request configuration
108
124
  * @param requestObj.request - Platform-specific purchase parameters
109
- * @param requestObj.type - Type of purchase: 'inapp' for products (default) or 'subs' for subscriptions
125
+ * @param requestObj.type - Type of purchase: 'in-app' for products (default) or 'subs' for subscriptions
110
126
  *
111
127
  * @example
112
128
  * ```typescript
@@ -116,7 +132,7 @@ type PurchaseRequest = {
116
132
  * ios: { sku: productId },
117
133
  * android: { skus: [productId] }
118
134
  * },
119
- * type: 'inapp'
135
+ * type: 'in-app'
120
136
  * });
121
137
  *
122
138
  * // Subscription purchase
@@ -132,11 +148,11 @@ type PurchaseRequest = {
132
148
  * });
133
149
  * ```
134
150
  */
135
- export declare const requestPurchase: (requestObj: PurchaseRequest) => Promise<Purchase | Purchase[] | void>;
151
+ export declare const requestPurchase: (requestObj: PurchaseRequestInput) => Promise<Purchase | Purchase[] | void>;
136
152
  export declare const finishTransaction: ({ purchase, isConsumable, }: {
137
153
  purchase: Purchase;
138
154
  isConsumable?: boolean;
139
- }) => Promise<PurchaseResult | boolean>;
155
+ }) => Promise<VoidResult | boolean>;
140
156
  /**
141
157
  * Retrieves the current storefront information from iOS App Store
142
158
  *
@@ -173,7 +189,7 @@ export declare const validateReceipt: (sku: string, androidOptions?: {
173
189
  productToken: string;
174
190
  accessToken: string;
175
191
  isSub?: boolean;
176
- }) => Promise<any>;
192
+ }) => Promise<ReceiptValidationResult>;
177
193
  /**
178
194
  * Deeplinks to native interface that allows users to manage their subscriptions
179
195
  * @param options.skuAndroid - Required for Android to locate specific subscription (ignored on iOS)
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAmBA,OAAO,EACL,OAAO,EACP,QAAQ,EAER,oBAAoB,EACpB,+BAA+B,EAC/B,mCAAmC,EACnC,mBAAmB,EAGnB,UAAU,EACV,uBAAuB,EACxB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAC,aAAa,EAAC,MAAM,kBAAkB,CAAC;AAG/C,cAAc,SAAS,CAAC;AACxB,OAAO,EAAC,cAAc,EAAE,gBAAgB,EAAC,MAAM,kBAAkB,CAAC;AAClE,cAAc,mBAAmB,CAAC;AAClC,cAAc,eAAe,CAAC;AAG9B,OAAO,EACL,sBAAsB,EACtB,sBAAsB,GACvB,MAAM,wBAAwB,CAAC;AAGhC,eAAO,MAAM,EAAE,KAAmB,CAAC;AAEnC,oBAAY,YAAY;IACtB,eAAe,qBAAqB;IACpC,aAAa,mBAAmB;IAChC,kBAAkB,yBAAyB;CAC5C;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,OAE1C;AAGD,eAAO,MAAM,OAAO,EAAoD;IACtE,WAAW,EAAE,CACX,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,KAC/B;QAAC,MAAM,EAAE,MAAM,IAAI,CAAA;KAAC,CAAC;IAC1B,cAAc,EAAE,CACd,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,KAC/B,IAAI,CAAC;CACX,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;AAC3D,MAAM,MAAM,cAAc,GAAG,OAAO,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAAC;AAE/D,KAAK,oBAAoB,GAAG;IAC1B,OAAO,EAAE,+BAA+B,CAAC;IACzC,IAAI,CAAC,EAAE,cAAc,CAAC;CACvB,CAAC;AAEF,KAAK,2BAA2B,GAAG;IACjC,OAAO,EAAE,mCAAmC,CAAC;IAC7C,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAC5B,oBAAoB,GACpB,2BAA2B,CAAC;AAEhC,MAAM,MAAM,eAAe,GACvB;IACE,OAAO,EAAE,oBAAoB,CAAC;IAC9B,IAAI,CAAC,EAAE,cAAc,CAAC;CACvB,GACD;IACE,OAAO,EAAE,mCAAmC,CAAC;IAC7C,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAwBN,eAAO,MAAM,uBAAuB,GAClC,UAAU,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI;YA5DrB,MAAM,IAAI;CAyEzB,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAChC,UAAU,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI;YA5E1B,MAAM,IAAI;CAyFzB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,0BAA0B,GACrC,UAAU,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI;YAhHtB,MAAM,IAAI;CAyHzB,CAAC;AAEF,wBAAgB,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC,CAGjD;AAED,wBAAsB,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC,CAEtD;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,aAAa,GAAU,iBAGjC;IACD,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,IAAI,CAAC,EAAE,gBAAgB,CAAC;CACzB,KAAG,OAAO,CAAC,OAAO,EAAE,GAAG,mBAAmB,EAAE,CAkD5C,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAAI,gEAGnC;IACD,6BAA6B,CAAC,EAAE,OAAO,CAAC;IACxC,yBAAyB,CAAC,EAAE,OAAO,CAAC;CAChC,KAAG,OAAO,CAAC,QAAQ,EAAE,CAUtB,CAAC;AAEN;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,gBAAgB,GAC3B,UAAS;IACP,6BAA6B,CAAC,EAAE,OAAO,CAAC;IACxC,yBAAyB,CAAC,EAAE,OAAO,CAAC;CAChC,KACL,OAAO,CAAC,QAAQ,EAAE,CAcpB,CAAC;AA4BF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,eAAO,MAAM,eAAe,GAC1B,YAAY,oBAAoB,KAC/B,OAAO,CAAC,QAAQ,GAAG,QAAQ,EAAE,GAAG,IAAI,CAkGtC,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,6BAG/B;IACD,QAAQ,EAAE,QAAQ,CAAC;IACnB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,KAAG,OAAO,CAAC,UAAU,GAAG,OAAO,CAsC/B,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,gBAAgB,QAAO,OAAO,CAAC,MAAM,CAMjD,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,QAAO,OAAO,CAAC,MAAM,CAS9C,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,GAC1B,KAAK,MAAM,EACX,iBAAiB;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,KACA,OAAO,CAAC,uBAAuB,CAwBjC,CAAC;AAEF;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,uBAAuB,GAAI,SAAS;IAC/C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B,KAAG,OAAO,CAAC,IAAI,CAaf,CAAC;AAEF,cAAc,UAAU,CAAC;AACzB,cAAc,sBAAsB,CAAC"}
package/build/index.js CHANGED
@@ -6,9 +6,11 @@ import ExpoIapModule from './ExpoIapModule';
6
6
  import { isProductIOS, validateReceiptIOS, deepLinkToSubscriptionsIOS, syncIOS, } from './modules/ios';
7
7
  import { isProductAndroid, validateReceiptAndroid, deepLinkToSubscriptionsAndroid, } from './modules/android';
8
8
  // Types
9
- import { PurchaseError, ErrorCode, } from './ExpoIap.types';
9
+ import { ErrorCode, } from './types';
10
+ import { PurchaseError } from './purchase-error';
10
11
  // Export all types
11
- export * from './ExpoIap.types';
12
+ export * from './types';
13
+ export { ErrorCodeUtils, ErrorCodeMapping } from './purchase-error';
12
14
  export * from './modules/android';
13
15
  export * from './modules/ios';
14
16
  // Export subscription helpers
@@ -26,6 +28,24 @@ export function setValueAsync(value) {
26
28
  }
27
29
  // Ensure the emitter has proper EventEmitter interface
28
30
  export const emitter = (ExpoIapModule || NativeModulesProxy.ExpoIap);
31
+ const normalizeProductType = (type) => {
32
+ if (type === 'inapp') {
33
+ console.warn("expo-iap: 'inapp' product type is deprecated and will be removed in v3.1.0. Use 'in-app' instead.");
34
+ }
35
+ if (!type || type === 'inapp' || type === 'in-app') {
36
+ return {
37
+ canonical: 'in-app',
38
+ native: 'inapp',
39
+ };
40
+ }
41
+ if (type === 'subs') {
42
+ return {
43
+ canonical: 'subs',
44
+ native: 'subs',
45
+ };
46
+ }
47
+ throw new Error(`Unsupported product type: ${type}`);
48
+ };
29
49
  export const purchaseUpdatedListener = (listener) => {
30
50
  console.log('[JS] Registering purchaseUpdatedListener');
31
51
  const wrappedListener = (event) => {
@@ -85,14 +105,14 @@ export async function endConnection() {
85
105
  *
86
106
  * @param params - Product fetch configuration
87
107
  * @param params.skus - Array of product SKUs to fetch
88
- * @param params.type - Type of products: 'inapp' for regular products (default) or 'subs' for subscriptions
108
+ * @param params.type - Type of products: 'in-app' for regular products (default) or 'subs' for subscriptions
89
109
  *
90
110
  * @example
91
111
  * ```typescript
92
112
  * // Regular products
93
113
  * const products = await fetchProducts({
94
114
  * skus: ['product1', 'product2'],
95
- * type: 'inapp'
115
+ * type: 'in-app'
96
116
  * });
97
117
  *
98
118
  * // Subscriptions
@@ -102,15 +122,16 @@ export async function endConnection() {
102
122
  * });
103
123
  * ```
104
124
  */
105
- export const fetchProducts = async ({ skus, type = 'inapp', }) => {
125
+ export const fetchProducts = async ({ skus, type, }) => {
106
126
  if (!skus?.length) {
107
127
  throw new PurchaseError({
108
128
  message: 'No SKUs provided',
109
129
  code: ErrorCode.EmptySkuList,
110
130
  });
111
131
  }
132
+ const { canonical, native } = normalizeProductType(type);
112
133
  if (Platform.OS === 'ios') {
113
- const rawItems = await ExpoIapModule.fetchProducts({ skus, type });
134
+ const rawItems = await ExpoIapModule.fetchProducts({ skus, type: native });
114
135
  const filteredItems = rawItems.filter((item) => {
115
136
  if (!isProductIOS(item)) {
116
137
  return false;
@@ -122,12 +143,12 @@ export const fetchProducts = async ({ skus, type = 'inapp', }) => {
122
143
  skus.includes(item.id);
123
144
  return isValid;
124
145
  });
125
- return type === 'inapp'
146
+ return canonical === 'in-app'
126
147
  ? filteredItems
127
148
  : filteredItems;
128
149
  }
129
150
  if (Platform.OS === 'android') {
130
- const items = await ExpoIapModule.fetchProducts(type, skus);
151
+ const items = await ExpoIapModule.fetchProducts(native, skus);
131
152
  const filteredItems = items.filter((item) => {
132
153
  if (!isProductAndroid(item))
133
154
  return false;
@@ -137,7 +158,7 @@ export const fetchProducts = async ({ skus, type = 'inapp', }) => {
137
158
  typeof item.id === 'string' &&
138
159
  skus.includes(item.id));
139
160
  });
140
- return type === 'inapp'
161
+ return canonical === 'in-app'
141
162
  ? filteredItems
142
163
  : filteredItems;
143
164
  }
@@ -195,7 +216,7 @@ const normalizeRequestProps = (request, platform) => {
195
216
  *
196
217
  * @param requestObj - Purchase request configuration
197
218
  * @param requestObj.request - Platform-specific purchase parameters
198
- * @param requestObj.type - Type of purchase: 'inapp' for products (default) or 'subs' for subscriptions
219
+ * @param requestObj.type - Type of purchase: 'in-app' for products (default) or 'subs' for subscriptions
199
220
  *
200
221
  * @example
201
222
  * ```typescript
@@ -205,7 +226,7 @@ const normalizeRequestProps = (request, platform) => {
205
226
  * ios: { sku: productId },
206
227
  * android: { skus: [productId] }
207
228
  * },
208
- * type: 'inapp'
229
+ * type: 'in-app'
209
230
  * });
210
231
  *
211
232
  * // Subscription purchase
@@ -222,7 +243,9 @@ const normalizeRequestProps = (request, platform) => {
222
243
  * ```
223
244
  */
224
245
  export const requestPurchase = (requestObj) => {
225
- const { request, type = 'inapp' } = requestObj;
246
+ const { request, type } = requestObj;
247
+ const { canonical, native } = normalizeProductType(type);
248
+ const isInAppPurchase = canonical === 'in-app';
226
249
  if (Platform.OS === 'ios') {
227
250
  const normalizedRequest = normalizeRequestProps(request, 'ios');
228
251
  if (!normalizedRequest?.sku) {
@@ -238,7 +261,7 @@ export const requestPurchase = (requestObj) => {
238
261
  quantity,
239
262
  withOffer: offer,
240
263
  });
241
- return type === 'inapp' ? purchase : purchase;
264
+ return purchase;
242
265
  })();
243
266
  }
244
267
  if (Platform.OS === 'android') {
@@ -246,11 +269,11 @@ export const requestPurchase = (requestObj) => {
246
269
  if (!normalizedRequest?.skus?.length) {
247
270
  throw new Error('Invalid request for Android. The `skus` property is required and must be a non-empty array.');
248
271
  }
249
- if (type === 'inapp') {
272
+ if (isInAppPurchase) {
250
273
  const { skus, obfuscatedAccountIdAndroid, obfuscatedProfileIdAndroid, isOfferPersonalized, } = normalizedRequest;
251
274
  return (async () => {
252
275
  return ExpoIapModule.requestPurchase({
253
- type: 'inapp',
276
+ type: native,
254
277
  skuArr: skus,
255
278
  purchaseToken: undefined,
256
279
  replacementMode: -1,
@@ -261,11 +284,11 @@ export const requestPurchase = (requestObj) => {
261
284
  });
262
285
  })();
263
286
  }
264
- if (type === 'subs') {
287
+ if (canonical === 'subs') {
265
288
  const { skus, obfuscatedAccountIdAndroid, obfuscatedProfileIdAndroid, isOfferPersonalized, subscriptionOffers = [], replacementModeAndroid = -1, purchaseToken, } = normalizedRequest;
266
289
  return (async () => {
267
290
  return ExpoIapModule.requestPurchase({
268
- type: 'subs',
291
+ type: native,
269
292
  skuArr: skus,
270
293
  purchaseToken,
271
294
  replacementMode: replacementModeAndroid,