expo-iap 2.0.0 → 2.2.0-rc.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.
Files changed (43) hide show
  1. package/README.md +15 -19
  2. package/android/src/main/java/expo/modules/iap/ExpoIapModule.kt +10 -9
  3. package/build/ExpoIap.types.d.ts +31 -40
  4. package/build/ExpoIap.types.d.ts.map +1 -1
  5. package/build/ExpoIap.types.js +0 -11
  6. package/build/ExpoIap.types.js.map +1 -1
  7. package/build/index.d.ts +3 -4
  8. package/build/index.d.ts.map +1 -1
  9. package/build/index.js +33 -46
  10. package/build/index.js.map +1 -1
  11. package/build/modules/android.d.ts +12 -4
  12. package/build/modules/android.d.ts.map +1 -1
  13. package/build/modules/android.js +8 -4
  14. package/build/modules/android.js.map +1 -1
  15. package/build/modules/ios.d.ts +16 -8
  16. package/build/modules/ios.d.ts.map +1 -1
  17. package/build/modules/ios.js +9 -5
  18. package/build/modules/ios.js.map +1 -1
  19. package/build/types/ExpoIapAndroid.types.d.ts +31 -31
  20. package/build/types/ExpoIapAndroid.types.d.ts.map +1 -1
  21. package/build/types/ExpoIapAndroid.types.js +6 -0
  22. package/build/types/ExpoIapAndroid.types.js.map +1 -1
  23. package/build/types/ExpoIapIos.types.d.ts +31 -34
  24. package/build/types/ExpoIapIos.types.d.ts.map +1 -1
  25. package/build/types/ExpoIapIos.types.js.map +1 -1
  26. package/build/useIap.d.ts +23 -0
  27. package/build/useIap.d.ts.map +1 -0
  28. package/build/useIap.js +100 -0
  29. package/build/useIap.js.map +1 -0
  30. package/bun.lockb +0 -0
  31. package/iap.md +161 -0
  32. package/ios/ExpoIapModule.swift +124 -31
  33. package/package.json +5 -5
  34. package/plugin/build/withIAP.d.ts +0 -3
  35. package/plugin/build/withIAP.js +31 -49
  36. package/plugin/src/withIAP.ts +39 -86
  37. package/src/ExpoIap.types.ts +48 -48
  38. package/src/index.ts +50 -96
  39. package/src/modules/android.ts +16 -12
  40. package/src/modules/ios.ts +21 -18
  41. package/src/types/ExpoIapAndroid.types.ts +37 -33
  42. package/src/types/ExpoIapIos.types.ts +36 -40
  43. package/src/useIap.ts +185 -0
@@ -1,38 +1,12 @@
1
1
  import {
2
2
  WarningAggregator,
3
- withAppBuildGradle,
3
+ withAndroidManifest,
4
4
  withProjectBuildGradle,
5
5
  } from 'expo/config-plugins';
6
6
  import {ConfigPlugin, createRunOncePlugin} from 'expo/config-plugins';
7
7
 
8
8
  const pkg = require('../../package.json');
9
9
 
10
- type PaymentProvider = 'Amazon AppStore' | 'both' | 'Play Store';
11
-
12
- const hasPaymentProviderProperValue = (
13
- paymentProvider: string,
14
- ): paymentProvider is PaymentProvider => {
15
- return ['Amazon AppStore', 'Play Store', 'both'].includes(paymentProvider);
16
- };
17
-
18
- const linesToAdd: {[key in PaymentProvider]: string} = {
19
- ['Amazon AppStore']: `missingDimensionStrategy "store", "amazon"`,
20
- ['Play Store']: `missingDimensionStrategy "store", "play"`,
21
- ['both']: `flavorDimensions "appstore"
22
-
23
- productFlavors {
24
- googlePlay {
25
- dimension "appstore"
26
- missingDimensionStrategy "store", "play"
27
- }
28
-
29
- amazon {
30
- dimension "appstore"
31
- missingDimensionStrategy "store", "amazon"
32
- }
33
- }`,
34
- };
35
-
36
10
  const addToBuildGradle = (
37
11
  newLine: string,
38
12
  anchor: RegExp | string,
@@ -41,37 +15,13 @@ const addToBuildGradle = (
41
15
  ) => {
42
16
  const lines = buildGradle.split('\n');
43
17
  const lineIndex = lines.findIndex((line) => line.match(anchor));
44
- // add after given line
45
- lines.splice(lineIndex + offset, 0, newLine);
46
- return lines.join('\n');
47
- };
48
-
49
- export const modifyAppBuildGradle = (
50
- buildGradle: string,
51
- paymentProvider: PaymentProvider,
52
- ) => {
53
- if (paymentProvider === 'both') {
54
- if (buildGradle.includes(`flavorDimensions "appstore"`)) {
55
- return buildGradle;
56
- }
57
- return addToBuildGradle(
58
- linesToAdd[paymentProvider],
59
- 'defaultConfig',
60
- -1,
61
- buildGradle,
62
- );
18
+ if (lineIndex === -1) {
19
+ console.warn('Anchor "ext" not found in build.gradle, appending to end');
20
+ lines.push(newLine);
21
+ } else {
22
+ lines.splice(lineIndex + offset, 0, newLine);
63
23
  }
64
-
65
- const missingDimensionStrategy = linesToAdd[paymentProvider];
66
- if (buildGradle.includes(missingDimensionStrategy)) {
67
- return buildGradle;
68
- }
69
- return addToBuildGradle(
70
- missingDimensionStrategy,
71
- 'defaultConfig',
72
- 1,
73
- buildGradle,
74
- );
24
+ return lines.join('\n');
75
25
  };
76
26
 
77
27
  export const modifyProjectBuildGradle = (buildGradle: string) => {
@@ -82,54 +32,57 @@ export const modifyProjectBuildGradle = (buildGradle: string) => {
82
32
  return addToBuildGradle(supportLibVersion, 'ext', 1, buildGradle);
83
33
  };
84
34
 
85
- const withIAPAndroid: ConfigPlugin<{paymentProvider: PaymentProvider}> = (
86
- config,
87
- {paymentProvider},
88
- ) => {
89
- // eslint-disable-next-line @typescript-eslint/no-shadow
90
- config = withAppBuildGradle(config, (config) => {
91
- config.modResults.contents = modifyAppBuildGradle(
35
+ const withIAPAndroid: ConfigPlugin = (config) => {
36
+ config = withProjectBuildGradle(config, (config) => {
37
+ config.modResults.contents = modifyProjectBuildGradle(
92
38
  config.modResults.contents,
93
- paymentProvider,
94
39
  );
95
40
  return config;
96
41
  });
97
42
 
98
- // eslint-disable-next-line @typescript-eslint/no-shadow
99
- config = withProjectBuildGradle(config, (config) => {
100
- config.modResults.contents = modifyProjectBuildGradle(
101
- config.modResults.contents,
102
- );
43
+ // Adding BILLING permission to AndroidManifest.xml
44
+ config = withAndroidManifest(config, (config) => {
45
+ console.log('Modifying AndroidManifest.xml...');
46
+ const manifest = config.modResults;
47
+
48
+ if (!manifest.manifest['uses-permission']) {
49
+ manifest.manifest['uses-permission'] = [];
50
+ }
51
+
52
+ const permissions = manifest.manifest['uses-permission'];
53
+ const billingPermission = {
54
+ $: {'android:name': 'com.android.vending.BILLING'},
55
+ };
56
+ if (
57
+ !permissions.some(
58
+ (perm: any) => perm.$['android:name'] === 'com.android.vending.BILLING',
59
+ )
60
+ ) {
61
+ permissions.push(billingPermission);
62
+ console.log('Added com.android.vending.BILLING to permissions');
63
+ } else {
64
+ console.log('com.android.vending.BILLING already exists in manifest');
65
+ }
66
+
103
67
  return config;
104
68
  });
69
+
105
70
  return config;
106
71
  };
107
72
 
108
- interface Props {
109
- paymentProvider?: PaymentProvider;
110
- }
73
+ interface Props {}
111
74
 
112
75
  const withIAP: ConfigPlugin<Props | undefined> = (config, props) => {
113
- const paymentProvider = props?.paymentProvider ?? 'Play Store';
114
-
115
- if (!hasPaymentProviderProperValue(paymentProvider)) {
116
- WarningAggregator.addWarningAndroid(
117
- 'expo-iap',
118
-
119
- `The payment provider '${paymentProvider}' is not supported. Please update your app.json file with one of the following supported values: 'Play Store', 'Amazon AppStore', or 'both'.`,
120
- );
121
- return config;
122
- }
123
76
  try {
124
- config = withIAPAndroid(config, {paymentProvider});
77
+ console.log('Applying expo-iap plugin...');
78
+ config = withIAPAndroid(config);
125
79
  } catch (error) {
126
80
  WarningAggregator.addWarningAndroid(
127
81
  'expo-iap',
128
-
129
82
  `There was a problem configuring expo-iap in your native Android project: ${error}`,
130
83
  );
84
+ console.error('Error in expo-iap plugin:', error);
131
85
  }
132
-
133
86
  return config;
134
87
  };
135
88
 
@@ -1,83 +1,83 @@
1
1
  import {
2
2
  ProductAndroid,
3
+ ProductPurchaseAndroid,
3
4
  RequestPurchaseAndroidProps,
4
5
  RequestSubscriptionAndroidProps,
5
6
  SubscriptionProductAndroid,
6
7
  } from './types/ExpoIapAndroid.types';
7
8
  import {
8
9
  ProductIos,
10
+ ProductPurchaseIos,
9
11
  RequestPurchaseIosProps,
10
12
  RequestSubscriptionIosProps,
11
13
  SubscriptionProductIos,
12
14
  } from './types/ExpoIapIos.types';
15
+
13
16
  export type ChangeEventPayload = {
14
17
  value: string;
15
18
  };
16
19
 
17
- export type Product = ProductAndroid | ProductIos;
20
+ /**
21
+ * Base product type with common properties shared between iOS and Android
22
+ */
23
+ export type ProductBase = {
24
+ id: string;
25
+ title: string;
26
+ description: string;
27
+ type: ProductType;
28
+ displayName?: string;
29
+ displayPrice?: string;
30
+ price?: number;
31
+ currency?: string;
32
+ };
33
+
34
+ // Define literal platform types for better type discrimination
35
+ export type IosPlatform = {platform: 'ios'};
36
+ export type AndroidPlatform = {platform: 'android'};
37
+
18
38
  export enum ProductType {
19
39
  InAppPurchase = 'inapp',
20
40
  Subscription = 'subs',
21
41
  }
22
42
 
23
- export type SubscriptionProduct =
24
- | SubscriptionProductAndroid
25
- | SubscriptionProductIos;
26
-
27
- export type RequestPurchaseProps =
28
- | RequestPurchaseIosProps
29
- | RequestPurchaseAndroidProps;
30
-
31
- enum PurchaseStateAndroid {
32
- UNSPECIFIED_STATE = 0,
33
- PURCHASED = 1,
34
- PENDING = 2,
35
- }
36
-
37
- export type ProductPurchase = {
38
- productId: string;
43
+ // Common base purchase type
44
+ export type PurchaseBase = {
45
+ id: string;
39
46
  transactionId?: string;
40
47
  transactionDate: number;
41
48
  transactionReceipt: string;
42
49
  purchaseToken?: string;
43
- //iOS
44
- quantityIOS?: number;
45
- originalTransactionDateIOS?: number;
46
- originalTransactionIdentifierIOS?: string;
47
- verificationResultIOS?: string;
48
- appAccountToken?: string;
49
- //Android
50
- productIds?: string[];
51
- dataAndroid?: string;
52
- signatureAndroid?: string;
53
- autoRenewingAndroid?: boolean;
54
- purchaseStateAndroid?: PurchaseStateAndroid;
55
- isAcknowledgedAndroid?: boolean;
56
- packageNameAndroid?: string;
57
- developerPayloadAndroid?: string;
58
- obfuscatedAccountIdAndroid?: string;
59
- obfuscatedProfileIdAndroid?: string;
60
50
  };
61
51
 
62
- export type RequestSubscriptionProps =
63
- | RequestSubscriptionAndroidProps
64
- | RequestSubscriptionIosProps;
52
+ // Union type for platform-specific product types with proper discriminators
53
+ export type Product =
54
+ | (ProductAndroid & AndroidPlatform)
55
+ | (ProductIos & IosPlatform);
65
56
 
66
- enum TransactionReason {
67
- PURCHASE = 'PURCHASE',
68
- RENEWAL = 'RENEWAL',
69
- }
57
+ // Union type for platform-specific purchase types with proper discriminators
58
+ export type ProductPurchase =
59
+ | (ProductPurchaseAndroid & AndroidPlatform)
60
+ | (ProductPurchaseIos & IosPlatform);
70
61
 
71
- export type SubscriptionPurchase = {
72
- autoRenewingAndroid?: boolean;
73
- originalTransactionDateIOS?: number;
74
- originalTransactionIdentifierIOS?: string;
75
- verificationResultIOS?: string;
76
- transactionReasonIOS?: TransactionReason | string;
77
- } & ProductPurchase;
62
+ // Union type for platform-specific subscription purchase types with proper discriminators
63
+ export type SubscriptionPurchase =
64
+ | (ProductPurchaseAndroid & AndroidPlatform & { autoRenewingAndroid: boolean })
65
+ | (ProductPurchaseIos & IosPlatform);
78
66
 
79
67
  export type Purchase = ProductPurchase | SubscriptionPurchase;
80
68
 
69
+ export type RequestPurchaseProps =
70
+ | RequestPurchaseIosProps
71
+ | RequestPurchaseAndroidProps;
72
+
73
+ export type SubscriptionProduct =
74
+ | (SubscriptionProductAndroid & AndroidPlatform)
75
+ | (SubscriptionProductIos & IosPlatform);
76
+
77
+ export type RequestSubscriptionProps =
78
+ | RequestSubscriptionAndroidProps
79
+ | RequestSubscriptionIosProps;
80
+
81
81
  export type PurchaseResult = {
82
82
  responseCode?: number;
83
83
  debugMessage?: string;
package/src/index.ts CHANGED
@@ -2,7 +2,6 @@
2
2
  // and on native platforms to ExpoIap.ts
3
3
  import {NativeModulesProxy, EventEmitter} from 'expo-modules-core';
4
4
  import {Platform} from 'react-native';
5
-
6
5
  import {
7
6
  Product,
8
7
  ProductPurchase,
@@ -21,13 +20,14 @@ import {
21
20
  } from './types/ExpoIapAndroid.types';
22
21
  import {
23
22
  PaymentDiscount,
24
- ProductIos,
25
23
  RequestPurchaseIosProps,
26
24
  RequestSubscriptionIosProps,
27
- SubscriptionProductIos,
28
- TransactionSk2,
29
25
  } from './types/ExpoIapIos.types';
30
- import {isProductIos} from './modules/ios';
26
+ import {isProductIos, isSubscriptionProductIos} from './modules/ios';
27
+ import {
28
+ isProductAndroid,
29
+ isSubscriptionProductAndroid,
30
+ } from './modules/android';
31
31
 
32
32
  export * from './modules/android';
33
33
  export * from './modules/ios';
@@ -70,22 +70,25 @@ export function initConnection() {
70
70
  }
71
71
 
72
72
  export const getProducts = async (skus: string[]): Promise<Product[]> => {
73
+ console.log('getProducts', skus);
73
74
  if (!skus?.length) {
74
75
  return Promise.reject(new Error('"skus" is required'));
75
76
  }
76
77
 
77
78
  return Platform.select({
78
79
  ios: async () => {
79
- const items = (await ExpoIapModule.getItems(skus)) as ProductIos[];
80
- return items.filter((item: ProductIos) => isProductIos(item));
80
+ const items = await ExpoIapModule.getItems(skus);
81
+ console.log('items', items);
82
+ return items.filter((item: unknown) => isProductIos<Product>(item));
81
83
  },
82
84
  android: async () => {
83
85
  const products = await ExpoIapModule.getItemsByType(
84
86
  ProductType.InAppPurchase,
85
87
  skus,
86
88
  );
87
-
88
- return products;
89
+ return products.filter((product: unknown) =>
90
+ isProductAndroid<Product>(product),
91
+ );
89
92
  },
90
93
  default: () => Promise.reject(new Error('Unsupported Platform')),
91
94
  })();
@@ -99,15 +102,33 @@ export const getSubscriptions = async (
99
102
  }
100
103
 
101
104
  return Platform.select({
102
- ios: async (): Promise<SubscriptionProductIos[]> => {
103
- const items: SubscriptionProductIos[] = (
104
- (await ExpoIapModule.getItems(skus)) as SubscriptionProductIos[]
105
- ).filter((item: SubscriptionProductIos) => skus.includes(item.id));
106
-
107
- return items;
105
+ ios: async () => {
106
+ const rawItems = await ExpoIapModule.getItems(skus);
107
+
108
+ return rawItems.filter((item: unknown) => {
109
+ if (!isSubscriptionProductIos(item)) return false;
110
+ return (
111
+ typeof item === 'object' &&
112
+ item !== null &&
113
+ 'id' in item &&
114
+ typeof item.id === 'string' &&
115
+ skus.includes(item.id)
116
+ );
117
+ }) as SubscriptionProduct[];
108
118
  },
109
119
  android: async () => {
110
- return ExpoIapModule.getItemsByType('subs', skus);
120
+ const rawItems = await ExpoIapModule.getItemsByType('subs', skus);
121
+
122
+ return rawItems.filter((item: unknown) => {
123
+ if (!isSubscriptionProductAndroid(item)) return false;
124
+ return (
125
+ typeof item === 'object' &&
126
+ item !== null &&
127
+ 'id' in item &&
128
+ typeof item.id === 'string' &&
129
+ skus.includes(item.id)
130
+ );
131
+ }) as SubscriptionProduct[];
111
132
  },
112
133
  default: () => Promise.reject(new Error('Unsupported Platform')),
113
134
  })();
@@ -119,11 +140,9 @@ export async function endConnection(): Promise<boolean> {
119
140
 
120
141
  export const getPurchaseHistory = ({
121
142
  alsoPublishToEventListener = false,
122
- automaticallyFinishRestoredTransactions = true,
123
143
  onlyIncludeActiveItems = false,
124
144
  }: {
125
145
  alsoPublishToEventListener?: boolean;
126
- automaticallyFinishRestoredTransactions?: boolean;
127
146
  onlyIncludeActiveItems?: boolean;
128
147
  } = {}): Promise<ProductPurchase[]> =>
129
148
  (
@@ -150,11 +169,9 @@ export const getPurchaseHistory = ({
150
169
 
151
170
  export const getAvailablePurchases = ({
152
171
  alsoPublishToEventListener = false,
153
- automaticallyFinishRestoredTransactions = false,
154
172
  onlyIncludeActiveItems = true,
155
173
  }: {
156
174
  alsoPublishToEventListener?: boolean;
157
- automaticallyFinishRestoredTransactions?: boolean;
158
175
  onlyIncludeActiveItems?: boolean;
159
176
  } = {}): Promise<ProductPurchase[]> =>
160
177
  (
@@ -193,43 +210,6 @@ const offerToRecordIos = (
193
210
  };
194
211
  };
195
212
 
196
- const iosTransactionToPurchaseMap = ({
197
- id,
198
- originalPurchaseDate,
199
- productID,
200
- purchaseDate,
201
- purchasedQuantity,
202
- originalID,
203
- verificationResult,
204
- appAccountToken,
205
- jsonRepresentation,
206
- }: TransactionSk2): Purchase => {
207
- let transactionReasonIOS;
208
-
209
- try {
210
- if (jsonRepresentation) {
211
- const transactionData = JSON.parse(jsonRepresentation);
212
- transactionReasonIOS = transactionData.transactionReason;
213
- }
214
- } catch (e) {
215
- console.log('SK2 Error parsing jsonRepresentation', e);
216
- }
217
- const purchase: Purchase = {
218
- productId: productID,
219
- transactionId: String(id),
220
- transactionDate: purchaseDate, //??
221
- transactionReceipt: '', // Not available
222
- purchaseToken: '', //Not available
223
- quantityIOS: purchasedQuantity,
224
- originalTransactionDateIOS: originalPurchaseDate,
225
- originalTransactionIdentifierIOS: originalID,
226
- verificationResultIOS: verificationResult ?? '',
227
- appAccountToken: appAccountToken ?? '',
228
- transactionReasonIOS: transactionReasonIOS ?? '',
229
- };
230
- return purchase;
231
- };
232
-
233
213
  export const requestPurchase = (
234
214
  request: RequestPurchaseIosProps | RequestPurchaseAndroidProps,
235
215
  ): Promise<ProductPurchase | ProductPurchase[] | void> =>
@@ -240,31 +220,17 @@ export const requestPurchase = (
240
220
  throw new Error('sku is required for iOS purchase');
241
221
  }
242
222
 
243
- const {
244
- sku,
245
- andDangerouslyFinishTransactionAutomaticallyIOS = false,
246
- appAccountToken,
247
- quantity,
248
- withOffer,
249
- } = request;
250
-
251
- if (andDangerouslyFinishTransactionAutomaticallyIOS) {
252
- console.warn(
253
- 'You are dangerously allowing expo-iap to finish your transaction automatically. You should set andDangerouslyFinishTransactionAutomatically to false when calling requestPurchase and call finishTransaction manually when you have delivered the purchased goods to the user. It defaults to true to provide backwards compatibility. Will default to false in version 4.0.0.',
254
- );
255
- }
223
+ const {sku, appAccountToken, quantity, withOffer} = request;
256
224
 
257
225
  const offer = offerToRecordIos(withOffer);
258
226
 
259
- const result = await ExpoIapModule.buyProduct(
227
+ const purchase = await ExpoIapModule.buyProduct(
260
228
  sku,
261
- andDangerouslyFinishTransactionAutomaticallyIOS,
262
229
  appAccountToken,
263
230
  quantity ?? -1,
264
231
  offer,
265
232
  );
266
233
 
267
- const purchase = iosTransactionToPurchaseMap(result);
268
234
  return Promise.resolve(purchase);
269
235
  },
270
236
  android: async () => {
@@ -303,35 +269,21 @@ export const requestSubscription = (
303
269
  throw new Error('sku is required for iOS subscriptions');
304
270
  }
305
271
 
306
- const {
307
- sku,
308
- andDangerouslyFinishTransactionAutomaticallyIOS = false,
309
- appAccountToken,
310
- quantity,
311
- withOffer,
312
- } = request as RequestSubscriptionIosProps;
313
-
314
- if (andDangerouslyFinishTransactionAutomaticallyIOS) {
315
- console.warn(
316
- 'You are dangerously allowing expo-iap to finish your transaction automatically. You should set andDangerouslyFinishTransactionAutomatically to false when calling requestPurchase and call finishTransaction manually when you have delivered the purchased goods to the user. It defaults to true to provide backwards compatibility. Will default to false in version 4.0.0.',
317
- );
318
- }
272
+ const {sku, appAccountToken, quantity, withOffer} =
273
+ request as RequestSubscriptionIosProps;
319
274
 
320
275
  const offer = offerToRecordIos(withOffer);
321
276
 
322
- const purchase = iosTransactionToPurchaseMap(
323
- await ExpoIapModule.buyProduct(
324
- sku,
325
- andDangerouslyFinishTransactionAutomaticallyIOS,
326
- appAccountToken,
327
- quantity ?? -1,
328
- offer,
329
- ),
277
+ const purchase = await ExpoIapModule.buyProduct(
278
+ sku,
279
+ appAccountToken,
280
+ quantity ?? -1,
281
+ offer,
330
282
  );
331
- return Promise.resolve(purchase);
283
+
284
+ return Promise.resolve(purchase as SubscriptionPurchase);
332
285
  },
333
286
  android: async () => {
334
- console.log('requestSubscription', request);
335
287
  const {
336
288
  skus,
337
289
  isOfferPersonalized,
@@ -398,3 +350,5 @@ export const finishTransaction = ({
398
350
  }) || (() => Promise.reject(new Error('Unsupported Platform')))
399
351
  )();
400
352
  };
353
+
354
+ export * from './useIap';
@@ -1,20 +1,24 @@
1
1
  import {Linking} from 'react-native';
2
- import {Product, PurchaseResult, SubscriptionProduct} from '../ExpoIap.types';
3
- import {
4
- ProductAndroid,
5
- ReceiptAndroid,
6
- SubscriptionProductAndroid,
7
- } from '../types/ExpoIapAndroid.types';
2
+ import {PurchaseResult} from '../ExpoIap.types';
3
+ import {ReceiptAndroid} from '../types/ExpoIapAndroid.types';
8
4
  import ExpoIapModule from '../ExpoIapModule';
9
5
 
10
- export function isProductAndroid(product: Product): product is ProductAndroid {
11
- return (product as ProductAndroid)?.title !== undefined;
6
+ // Type guards
7
+ export function isProductAndroid<T extends {platform?: string}>(
8
+ item: unknown,
9
+ ): item is T & {platform: 'android'} {
10
+ return (
11
+ item != null &&
12
+ typeof item === 'object' &&
13
+ 'platform' in item &&
14
+ item.platform === 'android'
15
+ );
12
16
  }
13
17
 
14
- export function isSubscriptionProductAndroid(
15
- product: SubscriptionProduct,
16
- ): product is SubscriptionProductAndroid {
17
- return (product as SubscriptionProductAndroid)?.title !== undefined;
18
+ export function isSubscriptionProductAndroid<T extends {platform?: string}>(
19
+ item: unknown,
20
+ ): item is T & {platform: 'android'} {
21
+ return isProductAndroid(item);
18
22
  }
19
23
 
20
24
  /**
@@ -1,16 +1,11 @@
1
1
  import {Platform} from 'react-native';
2
2
  import {emitter, IapEvent} from '..';
3
- import {Product, PurchaseError, SubscriptionProduct} from '../ExpoIap.types';
4
- import type {
5
- ProductIos,
6
- ProductStatusIos,
7
- SubscriptionProductIos,
8
- TransactionSk2,
9
- } from '../types/ExpoIapIos.types';
3
+ import {ProductPurchase, PurchaseError} from '../ExpoIap.types';
4
+ import type {ProductStatusIos} from '../types/ExpoIapIos.types';
10
5
  import ExpoIapModule from '../ExpoIapModule';
11
6
 
12
- type TransactionEvent = {
13
- transaction?: TransactionSk2;
7
+ export type TransactionEvent = {
8
+ transaction?: ProductPurchase;
14
9
  error?: PurchaseError;
15
10
  };
16
11
 
@@ -25,17 +20,25 @@ export const transactionUpdatedIos = (
25
20
  return emitter.addListener(IapEvent.TransactionIapUpdated, listener);
26
21
  };
27
22
 
28
- // Functions
29
- export function isProductIos(product: Product): product is ProductIos {
30
- return (product as ProductIos)?.displayName !== undefined;
23
+ // Type guards
24
+ export function isProductIos<T extends {platform?: string}>(
25
+ item: unknown,
26
+ ): item is T & {platform: 'ios'} {
27
+ return (
28
+ item != null &&
29
+ typeof item === 'object' &&
30
+ 'platform' in item &&
31
+ item.platform === 'ios'
32
+ );
31
33
  }
32
34
 
33
- export function isSubscriptionProductIos(
34
- product: SubscriptionProduct,
35
- ): product is SubscriptionProductIos {
36
- return (product as SubscriptionProductIos)?.displayName !== undefined;
35
+ export function isSubscriptionProductIos<T extends {platform?: string}>(
36
+ item: unknown,
37
+ ): item is T & {platform: 'ios'} {
38
+ return isProductIos(item);
37
39
  }
38
40
 
41
+ // Functions
39
42
  /**
40
43
  * Sync state with Appstore (iOS only)
41
44
  * https://developer.apple.com/documentation/storekit/appstore/3791906-sync
@@ -58,13 +61,13 @@ export const subscriptionStatus = (sku: string): Promise<ProductStatusIos[]> =>
58
61
  /**
59
62
  *
60
63
  */
61
- export const currentEntitlement = (sku: string): Promise<TransactionSk2> =>
64
+ export const currentEntitlement = (sku: string): Promise<ProductPurchase> =>
62
65
  ExpoIapModule.currentEntitlement(sku);
63
66
 
64
67
  /**
65
68
  *
66
69
  */
67
- export const latestTransaction = (sku: string): Promise<TransactionSk2> =>
70
+ export const latestTransaction = (sku: string): Promise<ProductPurchase> =>
68
71
  ExpoIapModule.latestTransaction(sku);
69
72
 
70
73
  /**