expo-iap 3.0.7 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/CLAUDE.md +14 -2
  2. package/CONTRIBUTING.md +19 -0
  3. package/README.md +18 -6
  4. package/android/build.gradle +24 -1
  5. package/android/src/main/java/expo/modules/iap/ExpoIapLog.kt +69 -0
  6. package/android/src/main/java/expo/modules/iap/ExpoIapModule.kt +190 -59
  7. package/build/index.d.ts +32 -111
  8. package/build/index.d.ts.map +1 -1
  9. package/build/index.js +198 -243
  10. package/build/index.js.map +1 -1
  11. package/build/modules/android.d.ts +7 -12
  12. package/build/modules/android.d.ts.map +1 -1
  13. package/build/modules/android.js +15 -12
  14. package/build/modules/android.js.map +1 -1
  15. package/build/modules/ios.d.ts +35 -36
  16. package/build/modules/ios.d.ts.map +1 -1
  17. package/build/modules/ios.js +101 -35
  18. package/build/modules/ios.js.map +1 -1
  19. package/build/types.d.ts +107 -82
  20. package/build/types.d.ts.map +1 -1
  21. package/build/types.js +1 -0
  22. package/build/types.js.map +1 -1
  23. package/build/useIAP.d.ts +7 -12
  24. package/build/useIAP.d.ts.map +1 -1
  25. package/build/useIAP.js +49 -23
  26. package/build/useIAP.js.map +1 -1
  27. package/build/utils/errorMapping.d.ts +32 -23
  28. package/build/utils/errorMapping.d.ts.map +1 -1
  29. package/build/utils/errorMapping.js +117 -22
  30. package/build/utils/errorMapping.js.map +1 -1
  31. package/ios/ExpoIap.podspec +3 -2
  32. package/ios/ExpoIapHelper.swift +96 -0
  33. package/ios/ExpoIapLog.swift +127 -0
  34. package/ios/ExpoIapModule.swift +218 -340
  35. package/openiap-versions.json +5 -0
  36. package/package.json +2 -2
  37. package/plugin/build/withIAP.js +6 -4
  38. package/plugin/src/withIAP.ts +14 -4
  39. package/scripts/update-types.mjs +20 -1
  40. package/src/index.ts +280 -356
  41. package/src/modules/android.ts +25 -23
  42. package/src/modules/ios.ts +138 -48
  43. package/src/types.ts +139 -91
  44. package/src/useIAP.ts +91 -58
  45. package/src/utils/errorMapping.ts +203 -23
  46. package/.copilot-instructions.md +0 -321
  47. package/.cursorrules +0 -321
  48. package/build/purchase-error.d.ts +0 -67
  49. package/build/purchase-error.d.ts.map +0 -1
  50. package/build/purchase-error.js +0 -166
  51. package/build/purchase-error.js.map +0 -1
  52. package/src/purchase-error.ts +0 -265
package/build/index.js CHANGED
@@ -3,29 +3,23 @@ import { NativeModulesProxy } from 'expo-modules-core';
3
3
  import { Platform } from 'react-native';
4
4
  // Internal modules
5
5
  import ExpoIapModule from './ExpoIapModule';
6
- import { isProductIOS, validateReceiptIOS, deepLinkToSubscriptionsIOS, syncIOS, } from './modules/ios';
6
+ import { isProductIOS, validateReceiptIOS, deepLinkToSubscriptionsIOS, syncIOS, getStorefrontIOS, } from './modules/ios';
7
7
  import { isProductAndroid, validateReceiptAndroid, deepLinkToSubscriptionsAndroid, } from './modules/android';
8
- // Types
9
- import { ErrorCode, } from './types';
10
- import { PurchaseError } from './purchase-error';
8
+ import { ErrorCode } from './types';
9
+ import { createPurchaseError } from './utils/errorMapping';
11
10
  // Export all types
12
11
  export * from './types';
13
- export { ErrorCodeUtils, ErrorCodeMapping } from './purchase-error';
14
12
  export * from './modules/android';
15
13
  export * from './modules/ios';
16
14
  // Export subscription helpers
17
15
  export { getActiveSubscriptions, hasActiveSubscriptions, } from './helpers/subscription';
18
16
  // Get the native constant value
19
- export const PI = ExpoIapModule.PI;
20
17
  export var OpenIapEvent;
21
18
  (function (OpenIapEvent) {
22
19
  OpenIapEvent["PurchaseUpdated"] = "purchase-updated";
23
20
  OpenIapEvent["PurchaseError"] = "purchase-error";
24
21
  OpenIapEvent["PromotedProductIOS"] = "promoted-product-ios";
25
22
  })(OpenIapEvent || (OpenIapEvent = {}));
26
- export function setValueAsync(value) {
27
- return ExpoIapModule.setValueAsync(value);
28
- }
29
23
  // Ensure the emitter has proper EventEmitter interface
30
24
  export const emitter = (ExpoIapModule ||
31
25
  NativeModulesProxy.ExpoIap);
@@ -36,7 +30,7 @@ const normalizeProductType = (type) => {
36
30
  if (!type || type === 'inapp' || type === 'in-app') {
37
31
  return {
38
32
  canonical: 'in-app',
39
- native: 'inapp',
33
+ native: 'in-app',
40
34
  };
41
35
  }
42
36
  if (type === 'subs') {
@@ -45,26 +39,39 @@ const normalizeProductType = (type) => {
45
39
  native: 'subs',
46
40
  };
47
41
  }
42
+ if (type === 'all') {
43
+ return {
44
+ canonical: 'all',
45
+ native: 'all',
46
+ };
47
+ }
48
48
  throw new Error(`Unsupported product type: ${type}`);
49
49
  };
50
+ const normalizePurchasePlatform = (purchase) => {
51
+ const platform = purchase.platform;
52
+ if (typeof platform !== 'string') {
53
+ return purchase;
54
+ }
55
+ const lowered = platform.toLowerCase();
56
+ if (lowered === platform || (lowered !== 'ios' && lowered !== 'android')) {
57
+ return purchase;
58
+ }
59
+ return { ...purchase, platform: lowered };
60
+ };
61
+ const normalizePurchaseArray = (purchases) => purchases.map((purchase) => normalizePurchasePlatform(purchase));
50
62
  export const purchaseUpdatedListener = (listener) => {
51
- console.log('[JS] Registering purchaseUpdatedListener');
52
63
  const wrappedListener = (event) => {
53
- console.log('[JS] purchaseUpdatedListener fired:', event);
54
- listener(event);
64
+ const normalized = normalizePurchasePlatform(event);
65
+ listener(normalized);
55
66
  };
56
67
  const emitterSubscription = emitter.addListener(OpenIapEvent.PurchaseUpdated, wrappedListener);
57
- console.log('[JS] purchaseUpdatedListener registered successfully');
58
68
  return emitterSubscription;
59
69
  };
60
70
  export const purchaseErrorListener = (listener) => {
61
- console.log('[JS] Registering purchaseErrorListener');
62
71
  const wrappedListener = (error) => {
63
- console.log('[JS] purchaseErrorListener fired:', error);
64
72
  listener(error);
65
73
  };
66
74
  const emitterSubscription = emitter.addListener(OpenIapEvent.PurchaseError, wrappedListener);
67
- console.log('[JS] purchaseErrorListener registered successfully');
68
75
  return emitterSubscription;
69
76
  };
70
77
  /**
@@ -94,116 +101,80 @@ export const promotedProductListenerIOS = (listener) => {
94
101
  }
95
102
  return emitter.addListener(OpenIapEvent.PromotedProductIOS, listener);
96
103
  };
97
- export function initConnection() {
98
- const result = ExpoIapModule.initConnection();
99
- return Promise.resolve(result);
100
- }
101
- export async function endConnection() {
102
- return ExpoIapModule.endConnection();
103
- }
104
+ export const initConnection = async () => ExpoIapModule.initConnection();
105
+ export const endConnection = async () => ExpoIapModule.endConnection();
104
106
  /**
105
107
  * Fetch products with unified API (v2.7.0+)
106
108
  *
107
- * @param params - Product fetch configuration
108
- * @param params.skus - Array of product SKUs to fetch
109
- * @param params.type - Type of products: 'in-app' for regular products (default) or 'subs' for subscriptions
110
- *
111
- * @example
112
- * ```typescript
113
- * // Regular products
114
- * const products = await fetchProducts({
115
- * skus: ['product1', 'product2'],
116
- * type: 'in-app'
117
- * });
118
- *
119
- * // Subscriptions
120
- * const subscriptions = await fetchProducts({
121
- * skus: ['sub1', 'sub2'],
122
- * type: 'subs'
123
- * });
124
- * ```
109
+ * @param request - Product fetch configuration
110
+ * @param request.skus - Array of product SKUs to fetch
111
+ * @param request.type - Product query type: 'in-app', 'subs', or 'all'
125
112
  */
126
- export const fetchProducts = async ({ skus, type, }) => {
127
- if (!skus?.length) {
128
- throw new PurchaseError({
113
+ export const fetchProducts = async (request) => {
114
+ console.log('fetchProducts called with:', request);
115
+ const { skus, type } = request ?? {};
116
+ if (!Array.isArray(skus) || skus.length === 0) {
117
+ throw createPurchaseError({
129
118
  message: 'No SKUs provided',
130
119
  code: ErrorCode.EmptySkuList,
131
120
  });
132
121
  }
133
122
  const { canonical, native } = normalizeProductType(type);
123
+ const skuSet = new Set(skus);
124
+ const filterIosItems = (items) => items.filter((item) => {
125
+ if (!isProductIOS(item)) {
126
+ return false;
127
+ }
128
+ const candidate = item;
129
+ return typeof candidate.id === 'string' && skuSet.has(candidate.id);
130
+ });
131
+ const filterAndroidItems = (items) => items.filter((item) => {
132
+ if (!isProductAndroid(item)) {
133
+ return false;
134
+ }
135
+ const candidate = item;
136
+ return typeof candidate.id === 'string' && skuSet.has(candidate.id);
137
+ });
138
+ const castResult = (items) => {
139
+ if (canonical === 'in-app') {
140
+ return items;
141
+ }
142
+ if (canonical === 'subs') {
143
+ return items;
144
+ }
145
+ return items;
146
+ };
134
147
  if (Platform.OS === 'ios') {
135
148
  const rawItems = await ExpoIapModule.fetchProducts({ skus, type: native });
136
- const filteredItems = rawItems.filter((item) => {
137
- if (!isProductIOS(item)) {
138
- return false;
139
- }
140
- const isValid = typeof item === 'object' &&
141
- item !== null &&
142
- 'id' in item &&
143
- typeof item.id === 'string' &&
144
- skus.includes(item.id);
145
- return isValid;
146
- });
147
- return canonical === 'in-app'
148
- ? filteredItems
149
- : filteredItems;
149
+ return castResult(filterIosItems(rawItems));
150
150
  }
151
151
  if (Platform.OS === 'android') {
152
- const items = await ExpoIapModule.fetchProducts(native, skus);
153
- const filteredItems = items.filter((item) => {
154
- if (!isProductAndroid(item))
155
- return false;
156
- return (typeof item === 'object' &&
157
- item !== null &&
158
- 'id' in item &&
159
- typeof item.id === 'string' &&
160
- skus.includes(item.id));
161
- });
162
- return canonical === 'in-app'
163
- ? filteredItems
164
- : filteredItems;
152
+ const rawItems = await ExpoIapModule.fetchProducts(native, skus);
153
+ return castResult(filterAndroidItems(rawItems));
165
154
  }
166
155
  throw new Error('Unsupported platform');
167
156
  };
168
- export const getAvailablePurchases = ({ alsoPublishToEventListenerIOS = false, onlyIncludeActiveItemsIOS = true, } = {}) => (Platform.select({
169
- ios: () => ExpoIapModule.getAvailableItems(alsoPublishToEventListenerIOS, onlyIncludeActiveItemsIOS),
170
- android: () => ExpoIapModule.getAvailableItems(),
171
- }) || (() => Promise.resolve([])))();
172
- /**
173
- * Restore completed transactions (cross-platform behavior)
174
- *
175
- * - iOS: perform a lightweight sync to refresh transactions and ignore sync errors,
176
- * then fetch available purchases to surface restored items to the app.
177
- * - Android: simply fetch available purchases (restoration happens via query).
178
- *
179
- * This helper returns the restored/available purchases so callers can update UI/state.
180
- *
181
- * @param options.alsoPublishToEventListenerIOS - iOS only: whether to also publish to the event listener
182
- * @param options.onlyIncludeActiveItemsIOS - iOS only: whether to only include active items
183
- * @returns Promise resolving to the list of available/restored purchases
184
- */
185
- export const restorePurchases = async (options = {}) => {
186
- if (Platform.OS === 'ios') {
187
- // Perform best-effort sync on iOS and ignore sync errors to avoid blocking restore flow
188
- await syncIOS().catch(() => undefined);
189
- }
190
- // Then, fetch available purchases for both platforms
191
- const purchases = await getAvailablePurchases({
192
- alsoPublishToEventListenerIOS: options.alsoPublishToEventListenerIOS ?? false,
193
- onlyIncludeActiveItemsIOS: options.onlyIncludeActiveItemsIOS ?? true,
194
- });
195
- return purchases;
196
- };
197
- const offerToRecordIOS = (offer) => {
198
- if (!offer)
199
- return undefined;
200
- return {
201
- identifier: offer.identifier,
202
- keyIdentifier: offer.keyIdentifier,
203
- nonce: offer.nonce,
204
- signature: offer.signature,
205
- timestamp: offer.timestamp.toString(),
157
+ export const getAvailablePurchases = async (options) => {
158
+ const normalizedOptions = {
159
+ alsoPublishToEventListenerIOS: options?.alsoPublishToEventListenerIOS ?? false,
160
+ onlyIncludeActiveItemsIOS: options?.onlyIncludeActiveItemsIOS ?? true,
206
161
  };
162
+ const resolvePurchases = Platform.select({
163
+ ios: () => ExpoIapModule.getAvailableItems(normalizedOptions.alsoPublishToEventListenerIOS, normalizedOptions.onlyIncludeActiveItemsIOS),
164
+ android: () => ExpoIapModule.getAvailableItems(),
165
+ }) ?? (() => Promise.resolve([]));
166
+ const purchases = await resolvePurchases();
167
+ return normalizePurchaseArray(purchases);
168
+ };
169
+ export const getStorefront = async () => {
170
+ // Cross-platform storefront
171
+ if (Platform.OS === 'android') {
172
+ if (typeof ExpoIapModule.getStorefrontAndroid === 'function') {
173
+ return ExpoIapModule.getStorefrontAndroid();
174
+ }
175
+ return '';
176
+ }
177
+ return getStorefrontIOS();
207
178
  };
208
179
  function normalizeRequestProps(request, platform) {
209
180
  // Platform-specific format - directly return the appropriate platform data
@@ -240,8 +211,8 @@ function normalizeRequestProps(request, platform) {
240
211
  * });
241
212
  * ```
242
213
  */
243
- export const requestPurchase = (requestObj) => {
244
- const { request, type } = requestObj;
214
+ export const requestPurchase = async (args) => {
215
+ const { request, type } = args;
245
216
  const { canonical, native } = normalizeProductType(type);
246
217
  const isInAppPurchase = canonical === 'in-app';
247
218
  if (Platform.OS === 'ios') {
@@ -249,18 +220,30 @@ export const requestPurchase = (requestObj) => {
249
220
  if (!normalizedRequest?.sku) {
250
221
  throw new Error('Invalid request for iOS. The `sku` property is required and must be a string.');
251
222
  }
252
- const { sku, andDangerouslyFinishTransactionAutomatically = false, appAccountToken, quantity, withOffer, } = normalizedRequest;
253
- return (async () => {
254
- const offer = offerToRecordIOS(withOffer ?? undefined);
255
- const purchase = await ExpoIapModule.requestPurchase({
256
- sku,
257
- andDangerouslyFinishTransactionAutomatically,
258
- appAccountToken,
259
- quantity,
260
- withOffer: offer,
261
- });
262
- return purchase;
263
- })();
223
+ let payload;
224
+ if (canonical === 'in-app') {
225
+ payload = {
226
+ type: 'in-app',
227
+ request: request,
228
+ };
229
+ }
230
+ else if (canonical === 'subs') {
231
+ payload = {
232
+ type: 'subs',
233
+ request: request,
234
+ };
235
+ }
236
+ else {
237
+ throw new Error(`Unsupported product type: ${canonical}`);
238
+ }
239
+ const purchase = (await ExpoIapModule.requestPurchase(payload));
240
+ if (Array.isArray(purchase)) {
241
+ return normalizePurchaseArray(purchase);
242
+ }
243
+ if (purchase) {
244
+ return normalizePurchasePlatform(purchase);
245
+ }
246
+ return canonical === 'subs' ? [] : null;
264
247
  }
265
248
  if (Platform.OS === 'android') {
266
249
  if (isInAppPurchase) {
@@ -269,18 +252,17 @@ export const requestPurchase = (requestObj) => {
269
252
  throw new Error('Invalid request for Android. The `skus` property is required and must be a non-empty array.');
270
253
  }
271
254
  const { skus, obfuscatedAccountIdAndroid, obfuscatedProfileIdAndroid, isOfferPersonalized, } = normalizedRequest;
272
- return (async () => {
273
- return ExpoIapModule.requestPurchase({
274
- type: native,
275
- skuArr: skus,
276
- purchaseToken: undefined,
277
- replacementMode: -1,
278
- obfuscatedAccountId: obfuscatedAccountIdAndroid,
279
- obfuscatedProfileId: obfuscatedProfileIdAndroid,
280
- offerTokenArr: [],
281
- isOfferPersonalized: isOfferPersonalized ?? false,
282
- });
283
- })();
255
+ const result = (await ExpoIapModule.requestPurchase({
256
+ type: native,
257
+ skuArr: skus,
258
+ purchaseToken: undefined,
259
+ replacementMode: -1,
260
+ obfuscatedAccountId: obfuscatedAccountIdAndroid,
261
+ obfuscatedProfileId: obfuscatedProfileIdAndroid,
262
+ offerTokenArr: [],
263
+ isOfferPersonalized: isOfferPersonalized ?? false,
264
+ }));
265
+ return normalizePurchaseArray(result);
284
266
  }
285
267
  if (canonical === 'subs') {
286
268
  const normalizedRequest = normalizeRequestProps(request, 'android');
@@ -291,90 +273,94 @@ export const requestPurchase = (requestObj) => {
291
273
  const normalizedOffers = subscriptionOffers ?? [];
292
274
  const replacementMode = replacementModeAndroid ?? -1;
293
275
  const purchaseToken = purchaseTokenAndroid ?? undefined;
294
- return (async () => {
295
- return ExpoIapModule.requestPurchase({
296
- type: native,
297
- skuArr: skus,
298
- purchaseToken,
299
- replacementMode,
300
- obfuscatedAccountId: obfuscatedAccountIdAndroid,
301
- obfuscatedProfileId: obfuscatedProfileIdAndroid,
302
- offerTokenArr: normalizedOffers.map((so) => so.offerToken),
303
- subscriptionOffers: normalizedOffers,
304
- isOfferPersonalized: isOfferPersonalized ?? false,
305
- });
306
- })();
276
+ const result = (await ExpoIapModule.requestPurchase({
277
+ type: native,
278
+ skuArr: skus,
279
+ purchaseToken,
280
+ replacementMode,
281
+ obfuscatedAccountId: obfuscatedAccountIdAndroid,
282
+ obfuscatedProfileId: obfuscatedProfileIdAndroid,
283
+ offerTokenArr: normalizedOffers.map((offer) => offer.offerToken),
284
+ subscriptionOffers: normalizedOffers,
285
+ isOfferPersonalized: isOfferPersonalized ?? false,
286
+ }));
287
+ return normalizePurchaseArray(result);
307
288
  }
308
289
  throw new Error("Invalid request for Android: Expected a valid request object with 'skus' array.");
309
290
  }
310
- return Promise.resolve(); // Fallback for unsupported platforms
291
+ throw new Error('Platform not supported');
311
292
  };
312
- export const finishTransaction = ({ purchase, isConsumable = false, }) => {
313
- return (Platform.select({
314
- ios: async () => {
315
- const transactionId = purchase.id;
316
- if (!transactionId) {
317
- return Promise.reject(new Error('purchase.id required to finish iOS transaction'));
318
- }
319
- await ExpoIapModule.finishTransaction(transactionId);
320
- return Promise.resolve(true);
321
- },
322
- android: async () => {
323
- const androidPurchase = purchase;
324
- // Use purchaseToken if available, fallback to purchaseTokenAndroid for backward compatibility
325
- const token = androidPurchase.purchaseToken;
326
- if (!token) {
327
- return Promise.reject(new PurchaseError({
328
- message: 'Purchase token is required to finish transaction',
329
- code: ErrorCode.DeveloperError,
330
- productId: androidPurchase.productId,
331
- platform: 'android',
332
- }));
333
- }
334
- if (isConsumable) {
335
- return ExpoIapModule.consumePurchaseAndroid(token);
336
- }
337
- return ExpoIapModule.acknowledgePurchaseAndroid(token);
338
- },
339
- }) || (() => Promise.reject(new Error('Unsupported Platform'))))();
293
+ export const finishTransaction = async ({ purchase, isConsumable = false, }) => {
294
+ if (Platform.OS === 'ios') {
295
+ await ExpoIapModule.finishTransaction(purchase, isConsumable);
296
+ return;
297
+ }
298
+ if (Platform.OS === 'android') {
299
+ const token = purchase.purchaseToken ?? undefined;
300
+ if (!token) {
301
+ throw createPurchaseError({
302
+ message: 'Purchase token is required to finish transaction',
303
+ code: ErrorCode.DeveloperError,
304
+ productId: purchase.productId,
305
+ platform: 'android',
306
+ });
307
+ }
308
+ if (isConsumable) {
309
+ await ExpoIapModule.consumePurchaseAndroid(token);
310
+ return;
311
+ }
312
+ await ExpoIapModule.acknowledgePurchaseAndroid(token);
313
+ return;
314
+ }
315
+ throw new Error('Unsupported Platform');
340
316
  };
341
317
  /**
342
- * Retrieves the current storefront information from iOS App Store
343
- *
344
- * @returns Promise resolving to the storefront country code
345
- * @throws Error if called on non-iOS platform
318
+ * Restore completed transactions (cross-platform behavior)
346
319
  *
347
- * @example
348
- * ```typescript
349
- * const storefront = await getStorefrontIOS();
350
- * console.log(storefront); // 'US'
351
- * ```
320
+ * - iOS: perform a lightweight sync to refresh transactions and ignore sync errors,
321
+ * then fetch available purchases to surface restored items to the app.
322
+ * - Android: simply fetch available purchases (restoration happens via query).
352
323
  *
353
- * @platform iOS
324
+ * This helper triggers the refresh flows but does not return the purchases; consumers should
325
+ * call `getAvailablePurchases` or rely on hook state to inspect the latest items.
354
326
  */
355
- export const getStorefrontIOS = () => {
356
- if (Platform.OS !== 'ios') {
357
- console.warn('getStorefrontIOS: This method is only available on iOS');
358
- return Promise.resolve('');
327
+ export const restorePurchases = async () => {
328
+ if (Platform.OS === 'ios') {
329
+ await syncIOS().catch(() => undefined);
359
330
  }
360
- return ExpoIapModule.getStorefrontIOS();
331
+ await getAvailablePurchases({
332
+ alsoPublishToEventListenerIOS: false,
333
+ onlyIncludeActiveItemsIOS: true,
334
+ });
361
335
  };
362
336
  /**
363
- * Gets the storefront country code from the underlying native store.
364
- * Returns a two-letter country code such as 'US', 'KR', or empty string on failure.
337
+ * Deeplinks to native interface that allows users to manage their subscriptions
338
+ * @param options.skuAndroid - Required for Android to locate specific subscription (ignored on iOS)
339
+ * @param options.packageNameAndroid - Required for Android to identify your app (ignored on iOS)
340
+ *
341
+ * @returns Promise that resolves when the deep link is successfully opened
342
+ *
343
+ * @throws {Error} When called on unsupported platform or when required Android parameters are missing
344
+ *
345
+ * @example
346
+ * import { deepLinkToSubscriptions } from 'expo-iap';
365
347
  *
366
- * @platform ios
367
- * @platform android
348
+ * // Works on both iOS and Android
349
+ * await deepLinkToSubscriptions({
350
+ * skuAndroid: 'your_subscription_sku',
351
+ * packageNameAndroid: 'com.example.app'
352
+ * });
368
353
  */
369
- export const getStorefront = () => {
370
- // Cross-platform storefront
354
+ export const deepLinkToSubscriptions = async (options) => {
355
+ if (Platform.OS === 'ios') {
356
+ await deepLinkToSubscriptionsIOS();
357
+ return;
358
+ }
371
359
  if (Platform.OS === 'android') {
372
- if (typeof ExpoIapModule.getStorefrontAndroid === 'function') {
373
- return ExpoIapModule.getStorefrontAndroid();
374
- }
375
- return Promise.resolve('');
360
+ await deepLinkToSubscriptionsAndroid(options ?? null);
361
+ return;
376
362
  }
377
- return getStorefrontIOS();
363
+ throw new Error(`Unsupported platform: ${Platform.OS}`);
378
364
  };
379
365
  /**
380
366
  * Internal receipt validation function (NOT RECOMMENDED for production use)
@@ -384,59 +370,28 @@ export const getStorefront = () => {
384
370
  * - iOS: Send receipt data to Apple's verification endpoint from your server
385
371
  * - Android: Use Google Play Developer API with service account credentials
386
372
  */
387
- export const validateReceipt = async (sku, androidOptions) => {
373
+ export const validateReceipt = async (options) => {
374
+ const { sku, androidOptions } = options;
388
375
  if (Platform.OS === 'ios') {
389
- return await validateReceiptIOS(sku);
376
+ return validateReceiptIOS({ sku });
390
377
  }
391
- else if (Platform.OS === 'android') {
378
+ if (Platform.OS === 'android') {
392
379
  if (!androidOptions ||
393
380
  !androidOptions.packageName ||
394
381
  !androidOptions.productToken ||
395
382
  !androidOptions.accessToken) {
396
383
  throw new Error('Android validation requires packageName, productToken, and accessToken');
397
384
  }
398
- return await validateReceiptAndroid({
385
+ return validateReceiptAndroid({
399
386
  packageName: androidOptions.packageName,
400
387
  productId: sku,
401
388
  productToken: androidOptions.productToken,
402
389
  accessToken: androidOptions.accessToken,
403
- isSub: androidOptions.isSub,
404
- });
405
- }
406
- else {
407
- throw new Error('Platform not supported');
408
- }
409
- };
410
- /**
411
- * Deeplinks to native interface that allows users to manage their subscriptions
412
- * @param options.skuAndroid - Required for Android to locate specific subscription (ignored on iOS)
413
- * @param options.packageNameAndroid - Required for Android to identify your app (ignored on iOS)
414
- *
415
- * @returns Promise that resolves when the deep link is successfully opened
416
- *
417
- * @throws {Error} When called on unsupported platform or when required Android parameters are missing
418
- *
419
- * @example
420
- * import { deepLinkToSubscriptions } from 'expo-iap';
421
- *
422
- * // Works on both iOS and Android
423
- * await deepLinkToSubscriptions({
424
- * skuAndroid: 'your_subscription_sku',
425
- * packageNameAndroid: 'com.example.app'
426
- * });
427
- */
428
- export const deepLinkToSubscriptions = (options) => {
429
- if (Platform.OS === 'ios') {
430
- return deepLinkToSubscriptionsIOS();
431
- }
432
- if (Platform.OS === 'android') {
433
- return deepLinkToSubscriptionsAndroid({
434
- sku: options?.skuAndroid,
435
- packageName: options?.packageNameAndroid,
390
+ isSub: androidOptions.isSub ?? undefined,
436
391
  });
437
392
  }
438
- return Promise.reject(new Error(`Unsupported platform: ${Platform.OS}`));
393
+ throw new Error('Platform not supported');
439
394
  };
440
395
  export * from './useIAP';
441
- export * from './utils/errorMapping';
396
+ export { ErrorCodeUtils, ErrorCodeMapping, createPurchaseError, createPurchaseErrorFromPlatform, } from './utils/errorMapping';
442
397
  //# sourceMappingURL=index.js.map