expo-iap 2.6.3 → 2.7.1

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/src/index.ts CHANGED
@@ -14,21 +14,17 @@ import {
14
14
  Purchase,
15
15
  PurchaseError,
16
16
  PurchaseResult,
17
- RequestSubscriptionProps,
17
+ RequestSubscriptionPropsWithLegacy,
18
+ RequestPurchasePropsWithLegacy,
18
19
  SubscriptionProduct,
19
20
  SubscriptionPurchase,
21
+ isPlatformRequestProps,
22
+ isUnifiedRequestProps,
20
23
  } from './ExpoIap.types';
21
- import {
22
- ProductPurchaseAndroid,
23
- RequestPurchaseAndroidProps,
24
- RequestSubscriptionAndroidProps,
25
- } from './types/ExpoIapAndroid.types';
26
- import {
27
- PaymentDiscount,
28
- RequestPurchaseIosProps,
29
- RequestSubscriptionIosProps,
30
- } from './types/ExpoIapIos.types';
24
+ import {ProductPurchaseAndroid} from './types/ExpoIapAndroid.types';
25
+ import {PaymentDiscount} from './types/ExpoIapIos.types';
31
26
 
27
+ // Export all types
32
28
  export * from './ExpoIap.types';
33
29
  export * from './modules/android';
34
30
  export * from './modules/ios';
@@ -81,6 +77,9 @@ export function initConnection() {
81
77
  }
82
78
 
83
79
  export const getProducts = async (skus: string[]): Promise<Product[]> => {
80
+ console.warn(
81
+ "`getProducts` is deprecated. Use `requestProducts({ skus, type: 'inapp' })` instead. This function will be removed in version 3.0.0.",
82
+ );
84
83
  if (!skus?.length) {
85
84
  return Promise.reject(new Error('"skus" is required'));
86
85
  }
@@ -112,6 +111,9 @@ export const getProducts = async (skus: string[]): Promise<Product[]> => {
112
111
  export const getSubscriptions = async (
113
112
  skus: string[],
114
113
  ): Promise<SubscriptionProduct[]> => {
114
+ console.warn(
115
+ "`getSubscriptions` is deprecated. Use `requestProducts({ skus, type: 'subs' })` instead. This function will be removed in version 3.0.0.",
116
+ );
115
117
  if (!skus?.length) {
116
118
  return Promise.reject(new Error('"skus" is required'));
117
119
  }
@@ -151,6 +153,78 @@ export async function endConnection(): Promise<boolean> {
151
153
  return ExpoIapModule.endConnection();
152
154
  }
153
155
 
156
+ /**
157
+ * Request products with unified API (v2.7.0+)
158
+ *
159
+ * @param params - Product request configuration
160
+ * @param params.skus - Array of product SKUs to fetch
161
+ * @param params.type - Type of products: 'inapp' for regular products (default) or 'subs' for subscriptions
162
+ *
163
+ * @example
164
+ * ```typescript
165
+ * // Regular products
166
+ * const products = await requestProducts({
167
+ * skus: ['product1', 'product2'],
168
+ * type: 'inapp'
169
+ * });
170
+ *
171
+ * // Subscriptions
172
+ * const subscriptions = await requestProducts({
173
+ * skus: ['sub1', 'sub2'],
174
+ * type: 'subs'
175
+ * });
176
+ * ```
177
+ */
178
+ export const requestProducts = async ({
179
+ skus,
180
+ type = 'inapp',
181
+ }: {
182
+ skus: string[];
183
+ type?: 'inapp' | 'subs';
184
+ }): Promise<Product[] | SubscriptionProduct[]> => {
185
+ if (!skus?.length) {
186
+ throw new Error('No SKUs provided');
187
+ }
188
+
189
+ if (Platform.OS === 'ios') {
190
+ const rawItems = await ExpoIapModule.getItems(skus);
191
+ const filteredItems = rawItems.filter((item: unknown) => {
192
+ if (!isProductIos(item)) return false;
193
+ return (
194
+ typeof item === 'object' &&
195
+ item !== null &&
196
+ 'id' in item &&
197
+ typeof item.id === 'string' &&
198
+ skus.includes(item.id)
199
+ );
200
+ });
201
+
202
+ return type === 'inapp'
203
+ ? (filteredItems as Product[])
204
+ : (filteredItems as SubscriptionProduct[]);
205
+ }
206
+
207
+ if (Platform.OS === 'android') {
208
+ const items = await ExpoIapModule.getItemsByType(type, skus);
209
+ const filteredItems = items.filter((item: unknown) => {
210
+ if (!isProductAndroid(item)) return false;
211
+ return (
212
+ typeof item === 'object' &&
213
+ item !== null &&
214
+ 'id' in item &&
215
+ typeof item.id === 'string' &&
216
+ skus.includes(item.id)
217
+ );
218
+ });
219
+
220
+ return type === 'inapp'
221
+ ? (filteredItems as Product[])
222
+ : (filteredItems as SubscriptionProduct[]);
223
+ }
224
+
225
+ throw new Error('Unsupported platform');
226
+ };
227
+
154
228
  /**
155
229
  * @deprecated Use `getPurchaseHistories` instead. This function will be removed in version 3.0.0.
156
230
  */
@@ -186,10 +260,12 @@ export const getPurchaseHistories = ({
186
260
  );
187
261
  },
188
262
  android: async () => {
189
- const products = await ExpoIapModule.getPurchaseHistoryByType('inapp');
190
- const subscriptions =
191
- await ExpoIapModule.getPurchaseHistoryByType('subs');
192
- return products.concat(subscriptions);
263
+ // getPurchaseHistoryByType was removed in Google Play Billing Library v8
264
+ // Android doesn't provide purchase history anymore, only active purchases
265
+ console.warn(
266
+ 'getPurchaseHistories is not supported on Android with Google Play Billing Library v8. Use getAvailablePurchases instead to get active purchases.',
267
+ );
268
+ return [];
193
269
  },
194
270
  }) || (() => Promise.resolve([]))
195
271
  )();
@@ -231,22 +307,98 @@ const offerToRecordIos = (
231
307
  };
232
308
 
233
309
  // Define discriminated union with explicit type parameter
310
+ // Using legacy types internally for backward compatibility
234
311
  type PurchaseRequest =
235
312
  | {
236
- request: RequestPurchaseIosProps | RequestPurchaseAndroidProps;
313
+ request: RequestPurchasePropsWithLegacy;
237
314
  type?: 'inapp';
238
315
  }
239
316
  | {
240
- request: RequestSubscriptionAndroidProps | RequestSubscriptionIosProps;
317
+ request: RequestSubscriptionPropsWithLegacy;
241
318
  type: 'subs';
242
319
  };
243
320
 
244
- // Type guards for request objects
245
- const isIosRequest = (
246
- request: any,
247
- ): request is RequestPurchaseIosProps | RequestSubscriptionIosProps =>
248
- 'sku' in request && typeof request.sku === 'string';
321
+ /**
322
+ * Helper to normalize request props to platform-specific format
323
+ */
324
+ const normalizeRequestProps = (
325
+ request: RequestPurchasePropsWithLegacy | RequestSubscriptionPropsWithLegacy,
326
+ platform: 'ios' | 'android',
327
+ ): any => {
328
+ // If it's already platform-specific format
329
+ if (isPlatformRequestProps(request)) {
330
+ return platform === 'ios' ? request.ios : request.android;
331
+ }
249
332
 
333
+ // If it's unified format, convert to platform-specific
334
+ if (isUnifiedRequestProps(request)) {
335
+ if (platform === 'ios') {
336
+ return {
337
+ sku: request.sku || (request.skus?.[0] ?? ''),
338
+ andDangerouslyFinishTransactionAutomaticallyIOS:
339
+ request.andDangerouslyFinishTransactionAutomaticallyIOS,
340
+ appAccountToken: request.appAccountToken,
341
+ quantity: request.quantity,
342
+ withOffer: request.withOffer,
343
+ };
344
+ } else {
345
+ const androidRequest: any = {
346
+ skus: request.skus || (request.sku ? [request.sku] : []),
347
+ obfuscatedAccountIdAndroid: request.obfuscatedAccountIdAndroid,
348
+ obfuscatedProfileIdAndroid: request.obfuscatedProfileIdAndroid,
349
+ isOfferPersonalized: request.isOfferPersonalized,
350
+ };
351
+
352
+ // Add subscription-specific fields if present
353
+ if ('subscriptionOffers' in request && request.subscriptionOffers) {
354
+ androidRequest.subscriptionOffers = request.subscriptionOffers;
355
+ }
356
+ if ('purchaseTokenAndroid' in request) {
357
+ androidRequest.purchaseTokenAndroid = request.purchaseTokenAndroid;
358
+ }
359
+ if ('replacementModeAndroid' in request) {
360
+ androidRequest.replacementModeAndroid = request.replacementModeAndroid;
361
+ }
362
+
363
+ return androidRequest;
364
+ }
365
+ }
366
+
367
+ // Legacy format handling
368
+ return request;
369
+ };
370
+
371
+ /**
372
+ * Request a purchase for products or subscriptions.
373
+ *
374
+ * @param requestObj - Purchase request configuration
375
+ * @param requestObj.request - Platform-specific purchase parameters
376
+ * @param requestObj.type - Type of purchase: 'inapp' for products (default) or 'subs' for subscriptions
377
+ *
378
+ * @example
379
+ * ```typescript
380
+ * // Product purchase
381
+ * await requestPurchase({
382
+ * request: {
383
+ * ios: { sku: productId },
384
+ * android: { skus: [productId] }
385
+ * },
386
+ * type: 'inapp'
387
+ * });
388
+ *
389
+ * // Subscription purchase
390
+ * await requestPurchase({
391
+ * request: {
392
+ * ios: { sku: subscriptionId },
393
+ * android: {
394
+ * skus: [subscriptionId],
395
+ * subscriptionOffers: [{ sku: subscriptionId, offerToken: 'token' }]
396
+ * }
397
+ * },
398
+ * type: 'subs'
399
+ * });
400
+ * ```
401
+ */
250
402
  export const requestPurchase = (
251
403
  requestObj: PurchaseRequest,
252
404
  ): Promise<
@@ -259,7 +411,9 @@ export const requestPurchase = (
259
411
  const {request, type = 'inapp'} = requestObj;
260
412
 
261
413
  if (Platform.OS === 'ios') {
262
- if (!isIosRequest(request)) {
414
+ const normalizedRequest = normalizeRequestProps(request, 'ios');
415
+
416
+ if (!normalizedRequest?.sku) {
263
417
  throw new Error(
264
418
  'Invalid request for iOS. The `sku` property is required and must be a string.',
265
419
  );
@@ -271,7 +425,7 @@ export const requestPurchase = (
271
425
  appAccountToken,
272
426
  quantity,
273
427
  withOffer,
274
- } = request;
428
+ } = normalizedRequest;
275
429
 
276
430
  return (async () => {
277
431
  const offer = offerToRecordIos(withOffer);
@@ -290,13 +444,21 @@ export const requestPurchase = (
290
444
  }
291
445
 
292
446
  if (Platform.OS === 'android') {
447
+ const normalizedRequest = normalizeRequestProps(request, 'android');
448
+
449
+ if (!normalizedRequest?.skus?.length) {
450
+ throw new Error(
451
+ 'Invalid request for Android. The `skus` property is required and must be a non-empty array.',
452
+ );
453
+ }
454
+
293
455
  if (type === 'inapp') {
294
456
  const {
295
457
  skus,
296
458
  obfuscatedAccountIdAndroid,
297
459
  obfuscatedProfileIdAndroid,
298
460
  isOfferPersonalized,
299
- } = request as RequestPurchaseAndroidProps;
461
+ } = normalizedRequest;
300
462
 
301
463
  return (async () => {
302
464
  return ExpoIapModule.buyItemByType({
@@ -321,7 +483,7 @@ export const requestPurchase = (
321
483
  subscriptionOffers = [],
322
484
  replacementModeAndroid = -1,
323
485
  purchaseTokenAndroid,
324
- } = request as RequestSubscriptionAndroidProps;
486
+ } = normalizedRequest;
325
487
 
326
488
  return (async () => {
327
489
  return ExpoIapModule.buyItemByType({
@@ -331,14 +493,14 @@ export const requestPurchase = (
331
493
  replacementMode: replacementModeAndroid,
332
494
  obfuscatedAccountId: obfuscatedAccountIdAndroid,
333
495
  obfuscatedProfileId: obfuscatedProfileIdAndroid,
334
- offerTokenArr: subscriptionOffers.map((so) => so.offerToken),
496
+ offerTokenArr: subscriptionOffers.map((so: any) => so.offerToken),
335
497
  isOfferPersonalized: isOfferPersonalized ?? false,
336
498
  }) as Promise<SubscriptionPurchase[]>;
337
499
  })();
338
500
  }
339
501
 
340
502
  throw new Error(
341
- "Invalid request for Android: Expected a 'RequestPurchaseAndroidProps' object with a valid 'skus' array or a 'RequestSubscriptionAndroidProps' object with 'skus' and 'subscriptionOffers'.",
503
+ "Invalid request for Android: Expected a valid request object with 'skus' array.",
342
504
  );
343
505
  }
344
506
 
@@ -346,13 +508,35 @@ export const requestPurchase = (
346
508
  };
347
509
 
348
510
  /**
349
- * @deprecated Use `requestPurchase({ request, type: 'subs' })` instead. This method will be removed in version 3.0.0+.
511
+ * @deprecated Use `requestPurchase({ request, type: 'subs' })` instead. This method will be removed in version 3.0.0.
512
+ *
513
+ * @example
514
+ * ```typescript
515
+ * // Old way (deprecated)
516
+ * await requestSubscription({
517
+ * sku: subscriptionId,
518
+ * // or for Android
519
+ * skus: [subscriptionId],
520
+ * });
521
+ *
522
+ * // New way (recommended)
523
+ * await requestPurchase({
524
+ * request: {
525
+ * ios: { sku: subscriptionId },
526
+ * android: {
527
+ * skus: [subscriptionId],
528
+ * subscriptionOffers: [{ sku: subscriptionId, offerToken: 'token' }]
529
+ * }
530
+ * },
531
+ * type: 'subs'
532
+ * });
533
+ * ```
350
534
  */
351
535
  export const requestSubscription = async (
352
- request: RequestSubscriptionProps,
536
+ request: RequestSubscriptionPropsWithLegacy,
353
537
  ): Promise<SubscriptionPurchase | SubscriptionPurchase[] | null | void> => {
354
538
  console.warn(
355
- "`requestSubscription` is deprecated. Use `requestPurchase({ request, type: 'subs' })` instead. This method will be removed in version 3.0.0+.",
539
+ "`requestSubscription` is deprecated and will be removed in version 3.0.0. Use `requestPurchase({ request, type: 'subs' })` instead.",
356
540
  );
357
541
  return (await requestPurchase({request, type: 'subs'})) as
358
542
  | SubscriptionPurchase
@@ -418,7 +602,8 @@ export const finishTransaction = ({
418
602
  */
419
603
  export const getStorefrontIOS = (): Promise<string> => {
420
604
  if (Platform.OS !== 'ios') {
421
- throw new Error('getStorefrontIOS: This method is only available on iOS');
605
+ console.warn('getStorefrontIOS: This method is only available on iOS');
606
+ return Promise.resolve('');
422
607
  }
423
608
  return ExpoIapModule.getStorefront();
424
609
  };
@@ -1,5 +1,4 @@
1
1
  // External dependencies
2
- import {Platform} from 'react-native';
3
2
 
4
3
  // Internal modules
5
4
  import {purchaseUpdatedListener} from '..';
@@ -39,10 +38,6 @@ export type TransactionEvent = {
39
38
  export const transactionUpdatedIos = (
40
39
  listener: (event: TransactionEvent) => void,
41
40
  ) => {
42
- if (Platform.OS !== 'ios') {
43
- throw new Error('This method is only available on iOS');
44
- }
45
-
46
41
  const isProductPurchase = (item: unknown): item is ProductPurchase => {
47
42
  return (
48
43
  item != null &&
@@ -100,9 +95,6 @@ export function isProductIos<T extends {platform?: string}>(
100
95
  * @platform iOS
101
96
  */
102
97
  export const syncIOS = (): Promise<null> => {
103
- if (Platform.OS !== 'ios') {
104
- throw new Error('syncIOS: This method is only available on iOS');
105
- }
106
98
  return ExpoIapModule.sync();
107
99
  };
108
100
 
@@ -118,11 +110,6 @@ export const syncIOS = (): Promise<null> => {
118
110
  export const isEligibleForIntroOfferIOS = (
119
111
  groupID: string,
120
112
  ): Promise<boolean> => {
121
- if (Platform.OS !== 'ios') {
122
- throw new Error(
123
- 'isEligibleForIntroOfferIOS: This method is only available on iOS',
124
- );
125
- }
126
113
  return ExpoIapModule.isEligibleForIntroOffer(groupID);
127
114
  };
128
115
 
@@ -138,11 +125,6 @@ export const isEligibleForIntroOfferIOS = (
138
125
  export const subscriptionStatusIOS = (
139
126
  sku: string,
140
127
  ): Promise<ProductStatusIos[]> => {
141
- if (Platform.OS !== 'ios') {
142
- throw new Error(
143
- 'subscriptionStatusIOS: This method is only available on iOS',
144
- );
145
- }
146
128
  return ExpoIapModule.subscriptionStatus(sku);
147
129
  };
148
130
 
@@ -158,11 +140,6 @@ export const subscriptionStatusIOS = (
158
140
  export const currentEntitlementIOS = (
159
141
  sku: string,
160
142
  ): Promise<ProductPurchase> => {
161
- if (Platform.OS !== 'ios') {
162
- throw new Error(
163
- 'currentEntitlementIOS: This method is only available on iOS',
164
- );
165
- }
166
143
  return ExpoIapModule.currentEntitlement(sku);
167
144
  };
168
145
 
@@ -176,11 +153,6 @@ export const currentEntitlementIOS = (
176
153
  * @platform iOS
177
154
  */
178
155
  export const latestTransactionIOS = (sku: string): Promise<ProductPurchase> => {
179
- if (Platform.OS !== 'ios') {
180
- throw new Error(
181
- 'latestTransactionIOS: This method is only available on iOS',
182
- );
183
- }
184
156
  return ExpoIapModule.latestTransaction(sku);
185
157
  };
186
158
 
@@ -197,11 +169,6 @@ type RefundRequestStatus = 'success' | 'userCancelled';
197
169
  export const beginRefundRequestIOS = (
198
170
  sku: string,
199
171
  ): Promise<RefundRequestStatus> => {
200
- if (Platform.OS !== 'ios') {
201
- throw new Error(
202
- 'beginRefundRequestIOS: This method is only available on iOS',
203
- );
204
- }
205
172
  return ExpoIapModule.beginRefundRequest(sku);
206
173
  };
207
174
 
@@ -216,11 +183,6 @@ export const beginRefundRequestIOS = (
216
183
  * @platform iOS
217
184
  */
218
185
  export const showManageSubscriptionsIOS = (): Promise<null> => {
219
- if (Platform.OS !== 'ios') {
220
- throw new Error(
221
- 'showManageSubscriptionsIOS: This method is only available on iOS',
222
- );
223
- }
224
186
  return ExpoIapModule.showManageSubscriptions();
225
187
  };
226
188
 
@@ -235,9 +197,6 @@ export const showManageSubscriptionsIOS = (): Promise<null> => {
235
197
  * @returns {Promise<string>} Base64 encoded receipt data
236
198
  */
237
199
  export const getReceiptIOS = (): Promise<string> => {
238
- if (Platform.OS !== 'ios') {
239
- throw new Error('This method is only available on iOS');
240
- }
241
200
  return ExpoIapModule.getReceiptData();
242
201
  };
243
202
 
@@ -252,11 +211,6 @@ export const getReceiptIOS = (): Promise<string> => {
252
211
  * @platform iOS
253
212
  */
254
213
  export const isTransactionVerifiedIOS = (sku: string): Promise<boolean> => {
255
- if (Platform.OS !== 'ios') {
256
- throw new Error(
257
- 'isTransactionVerifiedIOS: This method is only available on iOS',
258
- );
259
- }
260
214
  return ExpoIapModule.isTransactionVerified(sku);
261
215
  };
262
216
 
@@ -271,11 +225,6 @@ export const isTransactionVerifiedIOS = (sku: string): Promise<boolean> => {
271
225
  * @platform iOS
272
226
  */
273
227
  export const getTransactionJwsIOS = (sku: string): Promise<string> => {
274
- if (Platform.OS !== 'ios') {
275
- throw new Error(
276
- 'getTransactionJwsIOS: This method is only available on iOS',
277
- );
278
- }
279
228
  return ExpoIapModule.getTransactionJws(sku);
280
229
  };
281
230
 
@@ -302,10 +251,6 @@ export const validateReceiptIOS = async (
302
251
  jwsRepresentation: string;
303
252
  latestTransaction?: ProductPurchase;
304
253
  }> => {
305
- if (Platform.OS !== 'ios') {
306
- throw new Error('This method is only available on iOS');
307
- }
308
-
309
254
  const result = await ExpoIapModule.validateReceiptIOS(sku);
310
255
  return result;
311
256
  };
@@ -322,11 +267,6 @@ export const validateReceiptIOS = async (
322
267
  * @platform iOS
323
268
  */
324
269
  export const presentCodeRedemptionSheetIOS = (): Promise<boolean> => {
325
- if (Platform.OS !== 'ios') {
326
- throw new Error(
327
- 'presentCodeRedemptionSheetIOS: This method is only available on iOS',
328
- );
329
- }
330
270
  return ExpoIapModule.presentCodeRedemptionSheet();
331
271
  };
332
272
 
@@ -340,11 +280,6 @@ export const presentCodeRedemptionSheetIOS = (): Promise<boolean> => {
340
280
  * @platform iOS
341
281
  */
342
282
  export const getAppTransactionIOS = (): Promise<AppTransactionIOS | null> => {
343
- if (Platform.OS !== 'ios') {
344
- throw new Error(
345
- 'getAppTransactionIOS: This method is only available on iOS',
346
- );
347
- }
348
283
  return ExpoIapModule.getAppTransaction();
349
284
  };
350
285
 
package/src/useIap.ts CHANGED
@@ -15,6 +15,7 @@ import {
15
15
  finishTransaction as finishTransactionInternal,
16
16
  getSubscriptions,
17
17
  requestPurchase as requestPurchaseInternal,
18
+ requestProducts,
18
19
  } from './';
19
20
  import {syncIOS, validateReceiptIOS} from './modules/ios';
20
21
  import {validateReceiptAndroid} from './modules/android';
@@ -28,6 +29,8 @@ import {
28
29
  PurchaseResult,
29
30
  SubscriptionProduct,
30
31
  SubscriptionPurchase,
32
+ RequestPurchaseProps,
33
+ RequestSubscriptionProps,
31
34
  } from './ExpoIap.types';
32
35
 
33
36
  type UseIap = {
@@ -52,8 +55,12 @@ type UseIap = {
52
55
  getPurchaseHistories: (skus: string[]) => Promise<void>;
53
56
  getProducts: (skus: string[]) => Promise<void>;
54
57
  getSubscriptions: (skus: string[]) => Promise<void>;
58
+ requestProducts: (params: {
59
+ skus: string[];
60
+ type?: 'inapp' | 'subs';
61
+ }) => Promise<void>;
55
62
  requestPurchase: (params: {
56
- request: any;
63
+ request: RequestPurchaseProps | RequestSubscriptionProps;
57
64
  type?: 'inapp' | 'subs';
58
65
  }) => Promise<any>;
59
66
  validateReceipt: (
@@ -177,6 +184,37 @@ export function useIAP(options?: UseIAPOptions): UseIap {
177
184
  [mergeWithDuplicateCheck],
178
185
  );
179
186
 
187
+ const requestProductsInternal = useCallback(
188
+ async (params: {
189
+ skus: string[];
190
+ type?: 'inapp' | 'subs';
191
+ }): Promise<void> => {
192
+ try {
193
+ const result = await requestProducts(params);
194
+ if (params.type === 'subs') {
195
+ setSubscriptions((prevSubscriptions) =>
196
+ mergeWithDuplicateCheck(
197
+ prevSubscriptions,
198
+ result as SubscriptionProduct[],
199
+ (subscription) => subscription.id,
200
+ ),
201
+ );
202
+ } else {
203
+ setProducts((prevProducts) =>
204
+ mergeWithDuplicateCheck(
205
+ prevProducts,
206
+ result as Product[],
207
+ (product) => product.id,
208
+ ),
209
+ );
210
+ }
211
+ } catch (error) {
212
+ console.error('Error fetching products:', error);
213
+ }
214
+ },
215
+ [mergeWithDuplicateCheck],
216
+ );
217
+
180
218
  const getAvailablePurchasesInternal = useCallback(async (): Promise<void> => {
181
219
  try {
182
220
  const result = await getAvailablePurchases();
@@ -387,6 +425,7 @@ export function useIAP(options?: UseIAPOptions): UseIap {
387
425
  getPurchaseHistories: getPurchaseHistoriesInternal,
388
426
  getProducts: getProductsInternal,
389
427
  getSubscriptions: getSubscriptionsInternal,
428
+ requestProducts: requestProductsInternal,
390
429
  requestPurchase: requestPurchaseWithReset,
391
430
  validateReceipt,
392
431
  restorePurchases,