expo-iap 3.0.4 → 3.0.6

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 (45) hide show
  1. package/.eslintignore +1 -1
  2. package/.eslintrc.js +1 -0
  3. package/.prettierignore +1 -0
  4. package/CHANGELOG.md +10 -0
  5. package/CLAUDE.md +9 -1
  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/index.d.ts +28 -14
  11. package/build/index.d.ts.map +1 -1
  12. package/build/index.js +36 -15
  13. package/build/index.js.map +1 -1
  14. package/build/modules/android.d.ts +3 -4
  15. package/build/modules/android.d.ts.map +1 -1
  16. package/build/modules/android.js +2 -4
  17. package/build/modules/android.js.map +1 -1
  18. package/build/modules/ios.d.ts +2 -3
  19. package/build/modules/ios.d.ts.map +1 -1
  20. package/build/modules/ios.js +1 -3
  21. package/build/modules/ios.js.map +1 -1
  22. package/build/purchase-error.d.ts +8 -10
  23. package/build/purchase-error.d.ts.map +1 -1
  24. package/build/purchase-error.js +4 -2
  25. package/build/purchase-error.js.map +1 -1
  26. package/build/types.d.ts +159 -204
  27. package/build/types.d.ts.map +1 -1
  28. package/build/types.js +1 -59
  29. package/build/types.js.map +1 -1
  30. package/build/useIAP.d.ts +5 -12
  31. package/build/useIAP.d.ts.map +1 -1
  32. package/build/useIAP.js +10 -75
  33. package/build/useIAP.js.map +1 -1
  34. package/ios/ExpoIap.podspec +1 -1
  35. package/ios/ExpoIapModule.swift +103 -89
  36. package/package.json +2 -1
  37. package/plugin/build/withIAP.js +4 -5
  38. package/plugin/src/withIAP.ts +4 -5
  39. package/scripts/update-types.mjs +61 -0
  40. package/src/index.ts +77 -29
  41. package/src/modules/android.ts +5 -7
  42. package/src/modules/ios.ts +3 -5
  43. package/src/purchase-error.ts +13 -16
  44. package/src/types.ts +183 -216
  45. package/src/useIAP.ts +19 -94
package/src/index.ts CHANGED
@@ -22,6 +22,7 @@ import {
22
22
  Purchase,
23
23
  ErrorCode,
24
24
  RequestPurchaseProps,
25
+ RequestPurchasePropsByPlatforms,
25
26
  RequestSubscriptionPropsByPlatforms,
26
27
  ProductSubscription,
27
28
  PurchaseAndroid,
@@ -68,6 +69,58 @@ export const emitter = (ExpoIapModule || NativeModulesProxy.ExpoIap) as {
68
69
  ) => void;
69
70
  };
70
71
 
72
+ /**
73
+ * TODO(v3.1.0): Remove legacy 'inapp' alias once downstream apps migrate to 'in-app'.
74
+ */
75
+ export type ProductTypeInput = 'inapp' | 'in-app' | 'subs';
76
+ export type InAppTypeInput = Exclude<ProductTypeInput, 'subs'>;
77
+
78
+ type PurchaseRequestInApp = {
79
+ request: RequestPurchasePropsByPlatforms;
80
+ type?: InAppTypeInput;
81
+ };
82
+
83
+ type PurchaseRequestSubscription = {
84
+ request: RequestSubscriptionPropsByPlatforms;
85
+ type: 'subs';
86
+ };
87
+
88
+ export type PurchaseRequestInput =
89
+ | PurchaseRequestInApp
90
+ | PurchaseRequestSubscription;
91
+
92
+ export type PurchaseRequest =
93
+ | {
94
+ request: RequestPurchaseProps;
95
+ type?: InAppTypeInput;
96
+ }
97
+ | {
98
+ request: RequestSubscriptionPropsByPlatforms;
99
+ type: 'subs';
100
+ };
101
+
102
+ const normalizeProductType = (type?: ProductTypeInput) => {
103
+ if (type === 'inapp') {
104
+ console.warn(
105
+ "expo-iap: 'inapp' product type is deprecated and will be removed in v3.1.0. Use 'in-app' instead.",
106
+ );
107
+ }
108
+
109
+ if (!type || type === 'inapp' || type === 'in-app') {
110
+ return {
111
+ canonical: 'in-app' as const,
112
+ native: 'inapp' as const,
113
+ };
114
+ }
115
+ if (type === 'subs') {
116
+ return {
117
+ canonical: 'subs' as const,
118
+ native: 'subs' as const,
119
+ };
120
+ }
121
+ throw new Error(`Unsupported product type: ${type}`);
122
+ };
123
+
71
124
  export const purchaseUpdatedListener = (
72
125
  listener: (event: Purchase) => void,
73
126
  ) => {
@@ -146,14 +199,14 @@ export async function endConnection(): Promise<boolean> {
146
199
  *
147
200
  * @param params - Product fetch configuration
148
201
  * @param params.skus - Array of product SKUs to fetch
149
- * @param params.type - Type of products: 'inapp' for regular products (default) or 'subs' for subscriptions
202
+ * @param params.type - Type of products: 'in-app' for regular products (default) or 'subs' for subscriptions
150
203
  *
151
204
  * @example
152
205
  * ```typescript
153
206
  * // Regular products
154
207
  * const products = await fetchProducts({
155
208
  * skus: ['product1', 'product2'],
156
- * type: 'inapp'
209
+ * type: 'in-app'
157
210
  * });
158
211
  *
159
212
  * // Subscriptions
@@ -165,10 +218,10 @@ export async function endConnection(): Promise<boolean> {
165
218
  */
166
219
  export const fetchProducts = async ({
167
220
  skus,
168
- type = 'inapp',
221
+ type,
169
222
  }: {
170
223
  skus: string[];
171
- type?: 'inapp' | 'subs';
224
+ type?: ProductTypeInput;
172
225
  }): Promise<Product[] | ProductSubscription[]> => {
173
226
  if (!skus?.length) {
174
227
  throw new PurchaseError({
@@ -177,8 +230,10 @@ export const fetchProducts = async ({
177
230
  });
178
231
  }
179
232
 
233
+ const {canonical, native} = normalizeProductType(type);
234
+
180
235
  if (Platform.OS === 'ios') {
181
- const rawItems = await ExpoIapModule.fetchProducts({skus, type});
236
+ const rawItems = await ExpoIapModule.fetchProducts({skus, type: native});
182
237
 
183
238
  const filteredItems = rawItems.filter((item: unknown) => {
184
239
  if (!isProductIOS(item)) {
@@ -193,13 +248,13 @@ export const fetchProducts = async ({
193
248
  return isValid;
194
249
  });
195
250
 
196
- return type === 'inapp'
251
+ return canonical === 'in-app'
197
252
  ? (filteredItems as Product[])
198
253
  : (filteredItems as ProductSubscription[]);
199
254
  }
200
255
 
201
256
  if (Platform.OS === 'android') {
202
- const items = await ExpoIapModule.fetchProducts(type, skus);
257
+ const items = await ExpoIapModule.fetchProducts(native, skus);
203
258
  const filteredItems = items.filter((item: unknown) => {
204
259
  if (!isProductAndroid(item)) return false;
205
260
  return (
@@ -211,7 +266,7 @@ export const fetchProducts = async ({
211
266
  );
212
267
  });
213
268
 
214
- return type === 'inapp'
269
+ return canonical === 'in-app'
215
270
  ? (filteredItems as Product[])
216
271
  : (filteredItems as ProductSubscription[]);
217
272
  }
@@ -284,22 +339,13 @@ const offerToRecordIOS = (
284
339
  };
285
340
  };
286
341
 
287
- // Define discriminated union with explicit type parameter
288
- type PurchaseRequest =
289
- | {
290
- request: RequestPurchaseProps;
291
- type?: 'inapp';
292
- }
293
- | {
294
- request: RequestSubscriptionPropsByPlatforms;
295
- type: 'subs';
296
- };
297
-
298
342
  /**
299
343
  * Helper to normalize request props to platform-specific format
300
344
  */
301
345
  const normalizeRequestProps = (
302
- request: RequestPurchaseProps | RequestSubscriptionPropsByPlatforms,
346
+ request:
347
+ | RequestPurchasePropsByPlatforms
348
+ | RequestSubscriptionPropsByPlatforms,
303
349
  platform: 'ios' | 'android',
304
350
  ): any => {
305
351
  // Platform-specific format - directly return the appropriate platform data
@@ -311,7 +357,7 @@ const normalizeRequestProps = (
311
357
  *
312
358
  * @param requestObj - Purchase request configuration
313
359
  * @param requestObj.request - Platform-specific purchase parameters
314
- * @param requestObj.type - Type of purchase: 'inapp' for products (default) or 'subs' for subscriptions
360
+ * @param requestObj.type - Type of purchase: 'in-app' for products (default) or 'subs' for subscriptions
315
361
  *
316
362
  * @example
317
363
  * ```typescript
@@ -321,7 +367,7 @@ const normalizeRequestProps = (
321
367
  * ios: { sku: productId },
322
368
  * android: { skus: [productId] }
323
369
  * },
324
- * type: 'inapp'
370
+ * type: 'in-app'
325
371
  * });
326
372
  *
327
373
  * // Subscription purchase
@@ -338,9 +384,11 @@ const normalizeRequestProps = (
338
384
  * ```
339
385
  */
340
386
  export const requestPurchase = (
341
- requestObj: PurchaseRequest,
387
+ requestObj: PurchaseRequestInput,
342
388
  ): Promise<Purchase | Purchase[] | void> => {
343
- const {request, type = 'inapp'} = requestObj;
389
+ const {request, type} = requestObj;
390
+ const {canonical, native} = normalizeProductType(type);
391
+ const isInAppPurchase = canonical === 'in-app';
344
392
 
345
393
  if (Platform.OS === 'ios') {
346
394
  const normalizedRequest = normalizeRequestProps(request, 'ios');
@@ -369,7 +417,7 @@ export const requestPurchase = (
369
417
  withOffer: offer,
370
418
  });
371
419
 
372
- return type === 'inapp' ? (purchase as Purchase) : (purchase as Purchase);
420
+ return purchase as Purchase;
373
421
  })();
374
422
  }
375
423
 
@@ -382,7 +430,7 @@ export const requestPurchase = (
382
430
  );
383
431
  }
384
432
 
385
- if (type === 'inapp') {
433
+ if (isInAppPurchase) {
386
434
  const {
387
435
  skus,
388
436
  obfuscatedAccountIdAndroid,
@@ -392,7 +440,7 @@ export const requestPurchase = (
392
440
 
393
441
  return (async () => {
394
442
  return ExpoIapModule.requestPurchase({
395
- type: 'inapp',
443
+ type: native,
396
444
  skuArr: skus,
397
445
  purchaseToken: undefined,
398
446
  replacementMode: -1,
@@ -404,7 +452,7 @@ export const requestPurchase = (
404
452
  })();
405
453
  }
406
454
 
407
- if (type === 'subs') {
455
+ if (canonical === 'subs') {
408
456
  const {
409
457
  skus,
410
458
  obfuscatedAccountIdAndroid,
@@ -417,7 +465,7 @@ export const requestPurchase = (
417
465
 
418
466
  return (async () => {
419
467
  return ExpoIapModule.requestPurchase({
420
- type: 'subs',
468
+ type: native,
421
469
  skuArr: skus,
422
470
  purchaseToken,
423
471
  replacementMode: replacementModeAndroid,
@@ -6,18 +6,16 @@ import ExpoIapModule from '../ExpoIapModule';
6
6
 
7
7
  // Types
8
8
  import type {ReceiptValidationResultAndroid, VoidResult} from '../types';
9
- import {Platform as PurchasePlatform} from '../types';
10
9
 
11
10
  // Type guards
12
- export function isProductAndroid<
13
- T extends {platform?: string | PurchasePlatform},
14
- >(item: unknown): item is T & {platform: PurchasePlatform.Android | 'android'} {
11
+ export function isProductAndroid<T extends {platform?: string}>(
12
+ item: unknown,
13
+ ): item is T & {platform: 'android'} {
15
14
  return (
16
15
  item != null &&
17
16
  typeof item === 'object' &&
18
17
  'platform' in item &&
19
- ((item as any).platform === 'android' ||
20
- (item as any).platform === PurchasePlatform.Android)
18
+ (item as any).platform === 'android'
21
19
  );
22
20
  }
23
21
 
@@ -73,7 +71,7 @@ export const deepLinkToSubscriptionsAndroid = async ({
73
71
  * @param {string} params.productId - product id for your in app product.
74
72
  * @param {string} params.productToken - token for your purchase (called 'token' in the API documentation).
75
73
  * @param {string} params.accessToken - OAuth access token with androidpublisher scope. Required for authentication.
76
- * @param {boolean} params.isSub - whether this is subscription or inapp. `true` for subscription.
74
+ * @param {boolean} params.isSub - whether this is subscription or in-app. `true` for subscription.
77
75
  * @returns {Promise<ReceiptAndroid>}
78
76
  */
79
77
  export const validateReceiptAndroid = async ({
@@ -13,7 +13,6 @@ import type {
13
13
  ReceiptValidationResultIOS,
14
14
  } from '../types';
15
15
  import type {PurchaseError} from '../purchase-error';
16
- import {Platform as PurchasePlatform} from '../types';
17
16
  import {Linking} from 'react-native';
18
17
 
19
18
  export type TransactionEvent = {
@@ -24,15 +23,14 @@ export type TransactionEvent = {
24
23
  // Listeners
25
24
 
26
25
  // Type guards
27
- export function isProductIOS<T extends {platform?: string | PurchasePlatform}>(
26
+ export function isProductIOS<T extends {platform?: string}>(
28
27
  item: unknown,
29
- ): item is T & {platform: PurchasePlatform.Ios | 'ios'} {
28
+ ): item is T & {platform: 'ios'} {
30
29
  return (
31
30
  item != null &&
32
31
  typeof item === 'object' &&
33
32
  'platform' in item &&
34
- ((item as any).platform === 'ios' ||
35
- (item as any).platform === PurchasePlatform.Ios)
33
+ (item as any).platform === 'ios'
36
34
  );
37
35
  }
38
36
 
@@ -1,8 +1,5 @@
1
1
  import {NATIVE_ERROR_CODES} from './ExpoIapModule';
2
- import {ErrorCode, Platform} from './types';
3
-
4
- /** Platform identifiers supported by {@link PurchaseError}. */
5
- export type PurchaseErrorPlatform = Platform | 'ios' | 'android';
2
+ import {ErrorCode, IapPlatform} from './types';
6
3
 
7
4
  /** Properties used to construct a {@link PurchaseError}. */
8
5
  export interface PurchaseErrorProps {
@@ -11,7 +8,7 @@ export interface PurchaseErrorProps {
11
8
  debugMessage?: string;
12
9
  code?: ErrorCode;
13
10
  productId?: string;
14
- platform?: PurchaseErrorPlatform;
11
+ platform?: IapPlatform;
15
12
  }
16
13
 
17
14
  /** Shape of raw platform error objects coming from native modules. */
@@ -26,10 +23,10 @@ type PlatformErrorData = {
26
23
  const toStandardizedCode = (errorCode: ErrorCode): string =>
27
24
  errorCode.startsWith('E_') ? errorCode : `E_${errorCode}`;
28
25
 
29
- const normalizePlatform = (
30
- platform: PurchaseErrorPlatform,
31
- ): 'ios' | 'android' =>
32
- platform === Platform.Ios || platform === 'ios' ? 'ios' : 'android';
26
+ const normalizePlatform = (platform: IapPlatform): 'ios' | 'android' =>
27
+ typeof platform === 'string' && platform.toLowerCase() === 'ios'
28
+ ? 'ios'
29
+ : 'android';
33
30
 
34
31
  const OPENIAP_ERROR_CODE_SET: Set<string> = new Set(
35
32
  Object.values(ErrorCode).map((code) => toStandardizedCode(code)),
@@ -103,7 +100,7 @@ export class PurchaseError extends Error {
103
100
  public debugMessage?: string;
104
101
  public code?: ErrorCode;
105
102
  public productId?: string;
106
- public platform?: PurchaseErrorPlatform;
103
+ public platform?: IapPlatform;
107
104
 
108
105
  constructor(
109
106
  message: string,
@@ -111,7 +108,7 @@ export class PurchaseError extends Error {
111
108
  debugMessage?: string,
112
109
  code?: ErrorCode,
113
110
  productId?: string,
114
- platform?: PurchaseErrorPlatform,
111
+ platform?: IapPlatform,
115
112
  );
116
113
  constructor(props: PurchaseErrorProps);
117
114
  constructor(
@@ -120,7 +117,7 @@ export class PurchaseError extends Error {
120
117
  debugMessage?: string,
121
118
  code?: ErrorCode,
122
119
  productId?: string,
123
- platform?: PurchaseErrorPlatform,
120
+ platform?: IapPlatform,
124
121
  ) {
125
122
  super(
126
123
  typeof messageOrProps === 'string'
@@ -150,7 +147,7 @@ export class PurchaseError extends Error {
150
147
  */
151
148
  static fromPlatformError(
152
149
  errorData: PlatformErrorData,
153
- platform: PurchaseErrorPlatform,
150
+ platform: IapPlatform,
154
151
  ): PurchaseError {
155
152
  const normalizedPlatform = normalizePlatform(platform);
156
153
 
@@ -197,7 +194,7 @@ export const ErrorCodeUtils = {
197
194
  */
198
195
  fromPlatformCode: (
199
196
  platformCode: string | number,
200
- _platform: PurchaseErrorPlatform,
197
+ _platform: IapPlatform,
201
198
  ): ErrorCode => {
202
199
  if (typeof platformCode === 'string' && platformCode.startsWith('E_')) {
203
200
  if (OPENIAP_ERROR_CODE_SET.has(platformCode)) {
@@ -241,7 +238,7 @@ export const ErrorCodeUtils = {
241
238
  */
242
239
  toPlatformCode: (
243
240
  errorCode: ErrorCode,
244
- _platform: PurchaseErrorPlatform,
241
+ _platform: IapPlatform,
245
242
  ): string | number => {
246
243
  const standardized = toStandardizedCode(errorCode);
247
244
  const native = (NATIVE_ERROR_CODES as Record<string, string | number>)[
@@ -254,7 +251,7 @@ export const ErrorCodeUtils = {
254
251
  */
255
252
  isValidForPlatform: (
256
253
  errorCode: ErrorCode,
257
- platform: PurchaseErrorPlatform,
254
+ platform: IapPlatform,
258
255
  ): boolean => {
259
256
  const standardized = toStandardizedCode(errorCode);
260
257
  if (