expo-iap 2.2.7 → 2.2.8-rc.2

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.
@@ -34,11 +34,7 @@ export type ProductBase = {
34
34
  // Define literal platform types for better type discrimination
35
35
  export type IosPlatform = {platform: 'ios'};
36
36
  export type AndroidPlatform = {platform: 'android'};
37
-
38
- export enum ProductType {
39
- InAppPurchase = 'inapp',
40
- Subscription = 'subs',
41
- }
37
+ export type ProductType = 'inapp' | 'subs';
42
38
 
43
39
  // Common base purchase type
44
40
  export type PurchaseBase = {
package/src/index.ts CHANGED
@@ -5,7 +5,6 @@ import {Platform} from 'react-native';
5
5
  import {
6
6
  Product,
7
7
  ProductPurchase,
8
- ProductType,
9
8
  Purchase,
10
9
  PurchaseError,
11
10
  PurchaseResult,
@@ -24,16 +23,13 @@ import {
24
23
  RequestPurchaseIosProps,
25
24
  RequestSubscriptionIosProps,
26
25
  } from './types/ExpoIapIos.types';
27
- import {isProductIos, isSubscriptionProductIos} from './modules/ios';
28
- import {
29
- isProductAndroid,
30
- isSubscriptionProductAndroid,
31
- } from './modules/android';
26
+ import {isProductIos} from './modules/ios';
27
+ import {isProductAndroid} from './modules/android';
32
28
 
33
29
  export * from './modules/android';
34
30
  export * from './modules/ios';
35
31
 
36
- // Get the native constant value.
32
+ // Get the native constant value
37
33
  export const PI = ExpoIapModule.PI;
38
34
 
39
35
  export enum IapEvent {
@@ -81,11 +77,7 @@ export const getProducts = async (skus: string[]): Promise<Product[]> => {
81
77
  return items.filter((item: unknown) => isProductIos<Product>(item));
82
78
  },
83
79
  android: async () => {
84
- const products = await ExpoIapModule.getItemsByType(
85
- ProductType.InAppPurchase,
86
- skus,
87
- );
88
-
80
+ const products = await ExpoIapModule.getItemsByType('inapp', skus);
89
81
  return products.filter((product: unknown) =>
90
82
  isProductAndroid<Product>(product),
91
83
  );
@@ -105,7 +97,7 @@ export const getSubscriptions = async (
105
97
  ios: async () => {
106
98
  const rawItems = await ExpoIapModule.getItems(skus);
107
99
  return rawItems.filter((item: unknown) => {
108
- if (!isSubscriptionProductIos(item)) return false;
100
+ if (!isProductIos(item)) return false;
109
101
  return (
110
102
  typeof item === 'object' &&
111
103
  item !== null &&
@@ -118,7 +110,7 @@ export const getSubscriptions = async (
118
110
  android: async () => {
119
111
  const rawItems = await ExpoIapModule.getItemsByType('subs', skus);
120
112
  return rawItems.filter((item: unknown) => {
121
- if (!isSubscriptionProductAndroid(item)) return false;
113
+ if (!isProductAndroid(item)) return false;
122
114
  return (
123
115
  typeof item === 'object' &&
124
116
  item !== null &&
@@ -152,12 +144,9 @@ export const getPurchaseHistory = ({
152
144
  );
153
145
  },
154
146
  android: async () => {
155
- const products = await ExpoIapModule.getPurchaseHistoryByType(
156
- ProductType.InAppPurchase,
157
- );
158
- const subscriptions = await ExpoIapModule.getPurchaseHistoryByType(
159
- ProductType.Subscription,
160
- );
147
+ const products = await ExpoIapModule.getPurchaseHistoryByType('inapp');
148
+ const subscriptions =
149
+ await ExpoIapModule.getPurchaseHistoryByType('subs');
161
150
  return products.concat(subscriptions);
162
151
  },
163
152
  }) || (() => Promise.resolve([]))
@@ -178,12 +167,9 @@ export const getAvailablePurchases = ({
178
167
  onlyIncludeActiveItems,
179
168
  ),
180
169
  android: async () => {
181
- const products = await ExpoIapModule.getAvailableItemsByType(
182
- ProductType.InAppPurchase,
183
- );
184
- const subscriptions = await ExpoIapModule.getAvailableItemsByType(
185
- ProductType.Subscription,
186
- );
170
+ const products = await ExpoIapModule.getAvailableItemsByType('inapp');
171
+ const subscriptions =
172
+ await ExpoIapModule.getAvailableItemsByType('subs');
187
173
  return products.concat(subscriptions);
188
174
  },
189
175
  }) || (() => Promise.resolve([]))
@@ -192,9 +178,7 @@ export const getAvailablePurchases = ({
192
178
  const offerToRecordIos = (
193
179
  offer: PaymentDiscount | undefined,
194
180
  ): Record<keyof PaymentDiscount, string> | undefined => {
195
- if (!offer) {
196
- return undefined;
197
- }
181
+ if (!offer) return undefined;
198
182
  return {
199
183
  identifier: offer.identifier,
200
184
  keyIdentifier: offer.keyIdentifier,
@@ -204,44 +188,77 @@ const offerToRecordIos = (
204
188
  };
205
189
  };
206
190
 
191
+ // Define discriminated union with explicit type parameter
192
+ type PurchaseRequest =
193
+ | {
194
+ request: RequestPurchaseIosProps | RequestPurchaseAndroidProps;
195
+ type?: 'inapp';
196
+ }
197
+ | {
198
+ request: RequestSubscriptionAndroidProps | RequestSubscriptionIosProps;
199
+ type: 'subs';
200
+ };
201
+
202
+ // Type guards for request objects
203
+ const isIosRequest = (
204
+ request: any,
205
+ ): request is RequestPurchaseIosProps | RequestSubscriptionIosProps =>
206
+ 'sku' in request && typeof request.sku === 'string';
207
+
207
208
  export const requestPurchase = (
208
- request: RequestPurchaseIosProps | RequestPurchaseAndroidProps,
209
- ): Promise<ProductPurchase | ProductPurchase[] | void> =>
210
- (
211
- Platform.select({
212
- ios: async () => {
213
- if (!('sku' in request)) {
214
- throw new Error('sku is required for iOS purchase');
215
- }
216
- const {
217
- sku,
218
- andDangerouslyFinishTransactionAutomaticallyIOS = false,
219
- appAccountToken,
220
- quantity,
221
- withOffer,
222
- } = request;
223
- const offer = offerToRecordIos(withOffer);
224
- const purchase = await ExpoIapModule.buyProduct(
225
- sku,
226
- andDangerouslyFinishTransactionAutomaticallyIOS,
227
- appAccountToken,
228
- quantity ?? -1,
229
- offer,
230
- );
231
- return Promise.resolve(purchase);
232
- },
233
- android: async () => {
234
- if (!('skus' in request) || !request.skus.length) {
235
- throw new Error('skus is required for Android purchase');
236
- }
237
- const {
238
- skus,
239
- obfuscatedAccountIdAndroid,
240
- obfuscatedProfileIdAndroid,
241
- isOfferPersonalized,
242
- } = request;
209
+ requestObj: PurchaseRequest,
210
+ ): Promise<
211
+ | ProductPurchase
212
+ | SubscriptionPurchase
213
+ | ProductPurchase[]
214
+ | SubscriptionPurchase[]
215
+ | void
216
+ > => {
217
+ const {request, type = 'inapp'} = requestObj;
218
+
219
+ if (Platform.OS === 'ios') {
220
+ if (!isIosRequest(request)) {
221
+ throw new Error(
222
+ 'Invalid request for iOS. The `sku` property is required and must be a string.',
223
+ );
224
+ }
225
+
226
+ const {
227
+ sku,
228
+ andDangerouslyFinishTransactionAutomaticallyIOS = false,
229
+ appAccountToken,
230
+ quantity,
231
+ withOffer,
232
+ } = request;
233
+
234
+ return (async () => {
235
+ const offer = offerToRecordIos(withOffer);
236
+ const purchase = await ExpoIapModule.buyProduct(
237
+ sku,
238
+ andDangerouslyFinishTransactionAutomaticallyIOS,
239
+ appAccountToken,
240
+ quantity ?? -1,
241
+ offer,
242
+ );
243
+
244
+ return type === 'inapp'
245
+ ? (purchase as ProductPurchase)
246
+ : (purchase as SubscriptionPurchase);
247
+ })();
248
+ }
249
+
250
+ if (Platform.OS === 'android') {
251
+ if (type === 'inapp') {
252
+ const {
253
+ skus,
254
+ obfuscatedAccountIdAndroid,
255
+ obfuscatedProfileIdAndroid,
256
+ isOfferPersonalized,
257
+ } = request as RequestPurchaseAndroidProps;
258
+
259
+ return (async () => {
243
260
  return ExpoIapModule.buyItemByType({
244
- type: ProductType.InAppPurchase,
261
+ type: 'inapp',
245
262
  skuArr: skus,
246
263
  purchaseToken: undefined,
247
264
  replacementMode: -1,
@@ -249,60 +266,58 @@ export const requestPurchase = (
249
266
  obfuscatedProfileId: obfuscatedProfileIdAndroid,
250
267
  offerTokenArr: [],
251
268
  isOfferPersonalized: isOfferPersonalized ?? false,
252
- });
253
- },
254
- }) || Promise.resolve
255
- )();
269
+ }) as Promise<ProductPurchase[]>;
270
+ })();
271
+ }
256
272
 
257
- export const requestSubscription = (
258
- request: RequestSubscriptionProps,
259
- ): Promise<SubscriptionPurchase | SubscriptionPurchase[] | null | void> =>
260
- (
261
- Platform.select({
262
- ios: async () => {
263
- if (!('sku' in request)) {
264
- throw new Error('sku is required for iOS subscriptions');
265
- }
266
- const {
267
- sku,
268
- andDangerouslyFinishTransactionAutomaticallyIOS = false,
269
- appAccountToken,
270
- quantity,
271
- withOffer,
272
- } = request as RequestSubscriptionIosProps;
273
- const offer = offerToRecordIos(withOffer);
274
- const purchase = await ExpoIapModule.buyProduct(
275
- sku,
276
- andDangerouslyFinishTransactionAutomaticallyIOS,
277
- appAccountToken,
278
- quantity ?? -1,
279
- offer,
280
- );
281
- return Promise.resolve(purchase as SubscriptionPurchase);
282
- },
283
- android: async () => {
284
- const {
285
- skus,
286
- isOfferPersonalized,
287
- obfuscatedAccountIdAndroid,
288
- obfuscatedProfileIdAndroid,
289
- subscriptionOffers,
290
- replacementModeAndroid,
291
- purchaseTokenAndroid,
292
- } = request as RequestSubscriptionAndroidProps;
273
+ if (type === 'subs') {
274
+ const {
275
+ skus,
276
+ obfuscatedAccountIdAndroid,
277
+ obfuscatedProfileIdAndroid,
278
+ isOfferPersonalized,
279
+ subscriptionOffers = [],
280
+ replacementModeAndroid = -1,
281
+ purchaseTokenAndroid,
282
+ } = request as RequestSubscriptionAndroidProps;
283
+
284
+ return (async () => {
293
285
  return ExpoIapModule.buyItemByType({
294
- type: ProductType.Subscription,
295
- skuArr: skus.map((so) => so),
286
+ type: 'subs',
287
+ skuArr: skus,
296
288
  purchaseToken: purchaseTokenAndroid,
297
289
  replacementMode: replacementModeAndroid,
298
290
  obfuscatedAccountId: obfuscatedAccountIdAndroid,
299
291
  obfuscatedProfileId: obfuscatedProfileIdAndroid,
300
292
  offerTokenArr: subscriptionOffers.map((so) => so.offerToken),
301
293
  isOfferPersonalized: isOfferPersonalized ?? false,
302
- });
303
- },
304
- }) || (() => Promise.resolve(null))
305
- )();
294
+ }) as Promise<SubscriptionPurchase[]>;
295
+ })();
296
+ }
297
+
298
+ throw new Error(
299
+ "Invalid request for Android: Expected a 'RequestPurchaseAndroidProps' object with a valid 'skus' array or a 'RequestSubscriptionAndroidProps' object with 'skus' and 'subscriptionOffers'.",
300
+ );
301
+ }
302
+
303
+ return Promise.resolve(); // Fallback for unsupported platforms
304
+ };
305
+
306
+ /**
307
+ * @deprecated Use `requestPurchase({ request, type: 'subs' })` instead. This method will be removed in version 3.0.0+.
308
+ */
309
+ export const requestSubscription = async (
310
+ request: RequestSubscriptionProps,
311
+ ): Promise<SubscriptionPurchase | SubscriptionPurchase[] | null | void> => {
312
+ console.warn(
313
+ "`requestSubscription` is deprecated. Use `requestPurchase({ request, type: 'subs' })` instead. This method will be removed in version 3.0.0+.",
314
+ );
315
+ return (await requestPurchase({request, type: 'subs'})) as
316
+ | SubscriptionPurchase
317
+ | SubscriptionPurchase[]
318
+ | null
319
+ | void;
320
+ };
306
321
 
307
322
  export const finishTransaction = ({
308
323
  purchase,
@@ -15,12 +15,6 @@ export function isProductAndroid<T extends {platform?: string}>(
15
15
  );
16
16
  }
17
17
 
18
- export function isSubscriptionProductAndroid<T extends {platform?: string}>(
19
- item: unknown,
20
- ): item is T & {platform: 'android'} {
21
- return isProductAndroid(item);
22
- }
23
-
24
18
  /**
25
19
  * Deep link to subscriptions screen on Android.
26
20
  * @param {string} sku The product's SKU (on Android)
@@ -32,12 +32,6 @@ export function isProductIos<T extends {platform?: string}>(
32
32
  );
33
33
  }
34
34
 
35
- export function isSubscriptionProductIos<T extends {platform?: string}>(
36
- item: unknown,
37
- ): item is T & {platform: 'ios'} {
38
- return isProductIos(item);
39
- }
40
-
41
35
  // Functions
42
36
  /**
43
37
  * Sync state with Appstore (iOS only)
package/src/useIap.ts CHANGED
@@ -9,6 +9,7 @@ import {
9
9
  getPurchaseHistory,
10
10
  finishTransaction as finishTransactionInternal,
11
11
  getSubscriptions,
12
+ requestPurchase as requestPurchaseInternal,
12
13
  } from './';
13
14
  import {useCallback, useEffect, useState, useRef} from 'react';
14
15
  import {
@@ -40,10 +41,11 @@ type IAP_STATUS = {
40
41
  purchase: Purchase;
41
42
  isConsumable?: boolean;
42
43
  }) => Promise<string | boolean | PurchaseResult | void>;
43
- getAvailablePurchases: () => Promise<void>;
44
- getPurchaseHistories: () => Promise<void>;
44
+ getAvailablePurchases: (skus: string[]) => Promise<void>;
45
+ getPurchaseHistories: (skus: string[]) => Promise<void>;
45
46
  getProducts: (skus: string[]) => Promise<void>;
46
47
  getSubscriptions: (skus: string[]) => Promise<void>;
48
+ requestPurchase: typeof requestPurchaseInternal;
47
49
  };
48
50
 
49
51
  export function useIAP(): IAP_STATUS {
@@ -63,29 +65,31 @@ export function useIAP(): IAP_STATUS {
63
65
  const [currentPurchaseError, setCurrentPurchaseError] =
64
66
  useState<PurchaseError>();
65
67
 
66
- // 구독을 훅 인스턴스별로 관리하기 위한 ref
67
68
  const subscriptionsRef = useRef<{
68
69
  purchaseUpdate?: Subscription;
69
70
  purchaseError?: Subscription;
70
71
  promotedProductsIos?: Subscription;
71
72
  }>({});
72
73
 
73
- const requestProducts = useCallback(async (skus: string[]): Promise<void> => {
74
- setProducts(await getProducts(skus));
75
- }, []);
74
+ const getProductsInternal = useCallback(
75
+ async (skus: string[]): Promise<void> => {
76
+ setProducts(await getProducts(skus));
77
+ },
78
+ [],
79
+ );
76
80
 
77
- const requestSubscriptions = useCallback(
81
+ const getSubscriptionsInternal = useCallback(
78
82
  async (skus: string[]): Promise<void> => {
79
83
  setSubscriptions(await getSubscriptions(skus));
80
84
  },
81
85
  [],
82
86
  );
83
87
 
84
- const requestAvailablePurchases = useCallback(async (): Promise<void> => {
88
+ const getAvailablePurchasesInternal = useCallback(async (): Promise<void> => {
85
89
  setAvailablePurchases(await getAvailablePurchases());
86
90
  }, []);
87
91
 
88
- const requestPurchaseHistories = useCallback(async (): Promise<void> => {
92
+ const getPurchaseHistoriesInternal = useCallback(async (): Promise<void> => {
89
93
  setPurchaseHistories(await getPurchaseHistory());
90
94
  }, []);
91
95
 
@@ -172,9 +176,10 @@ export function useIAP(): IAP_STATUS {
172
176
  availablePurchases,
173
177
  currentPurchase,
174
178
  currentPurchaseError,
175
- getProducts: requestProducts,
176
- getSubscriptions: requestSubscriptions,
177
- getAvailablePurchases: requestAvailablePurchases,
178
- getPurchaseHistories: requestPurchaseHistories,
179
+ getAvailablePurchases: getAvailablePurchasesInternal,
180
+ getPurchaseHistories: getPurchaseHistoriesInternal,
181
+ getProducts: getProductsInternal,
182
+ getSubscriptions: getSubscriptionsInternal,
183
+ requestPurchase: requestPurchaseInternal,
179
184
  };
180
185
  }