expo-iap 2.7.10 → 2.7.12

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.
@@ -48,9 +48,10 @@ func serializeTransaction(_ transaction: Transaction, jwsRepresentationIos: Stri
48
48
  }
49
49
 
50
50
  var purchaseMap: [String: Any?] = [
51
- "id": transaction.productID,
51
+ "id": String(transaction.id),
52
+ "productId": transaction.productID,
52
53
  "ids": [transaction.productID],
53
- "transactionId": String(transaction.id),
54
+ "transactionId": String(transaction.id), // @deprecated - use id instead
54
55
  "transactionDate": transaction.purchaseDate.timeIntervalSince1970 * 1000,
55
56
  "transactionReceipt": jwsReceipt,
56
57
  "platform": "ios",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-iap",
3
- "version": "2.7.10",
3
+ "version": "2.7.12",
4
4
  "description": "In App Purchase module in Expo",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -1 +1 @@
1
- {"root":["./src/withIAP.ts"],"version":"5.8.3"}
1
+ {"root":["./src/withIAP.ts"],"version":"5.9.2"}
@@ -1,15 +1,11 @@
1
1
  import {
2
2
  ProductAndroid,
3
3
  ProductPurchaseAndroid,
4
- RequestPurchaseAndroidProps,
5
- RequestSubscriptionAndroidProps,
6
4
  SubscriptionProductAndroid,
7
5
  } from './types/ExpoIapAndroid.types';
8
6
  import {
9
7
  ProductIos,
10
8
  ProductPurchaseIos,
11
- RequestPurchaseIosProps,
12
- RequestSubscriptionIosProps,
13
9
  SubscriptionProductIos,
14
10
  } from './types/ExpoIapIos.types';
15
11
  import {NATIVE_ERROR_CODES} from './ExpoIapModule';
@@ -32,8 +28,9 @@ export type ProductBase = {
32
28
  };
33
29
 
34
30
  export type PurchaseBase = {
35
- id: string;
36
- transactionId?: string;
31
+ id: string; // Transaction identifier - used by finishTransaction
32
+ productId: string; // Product identifier - which product was purchased
33
+ transactionId?: string; // @deprecated - use id instead
37
34
  transactionDate: number;
38
35
  transactionReceipt: string;
39
36
  };
@@ -51,15 +48,6 @@ export type SubscriptionProduct =
51
48
  | (SubscriptionProductAndroid & AndroidPlatform)
52
49
  | (SubscriptionProductIos & IosPlatform);
53
50
 
54
- // Legacy internal platform-specific types (kept for backward compatibility)
55
- export type LegacyRequestPurchaseProps =
56
- | RequestPurchaseIosProps
57
- | RequestPurchaseAndroidProps;
58
-
59
- export type LegacyRequestSubscriptionProps =
60
- | RequestSubscriptionAndroidProps
61
- | RequestSubscriptionIosProps;
62
-
63
51
  // ============================================================================
64
52
  // Legacy Types (For backward compatibility with useIap hook)
65
53
  // ============================================================================
@@ -323,20 +311,6 @@ export interface UnifiedRequestPurchaseProps {
323
311
  readonly isOfferPersonalized?: boolean;
324
312
  }
325
313
 
326
- /**
327
- * Unified subscription request props
328
- */
329
- export interface UnifiedRequestSubscriptionProps
330
- extends UnifiedRequestPurchaseProps {
331
- // Android subscription-specific properties
332
- readonly purchaseTokenAndroid?: string;
333
- readonly replacementModeAndroid?: number;
334
- readonly subscriptionOffers?: {
335
- sku: string;
336
- offerToken: string;
337
- }[];
338
- }
339
-
340
314
  // ============================================================================
341
315
  // New Platform-Specific Request Types (v2.7.0+)
342
316
  // ============================================================================
@@ -344,7 +318,7 @@ export interface UnifiedRequestSubscriptionProps
344
318
  /**
345
319
  * iOS-specific purchase request parameters
346
320
  */
347
- export interface IosRequestPurchaseProps {
321
+ export interface RequestPurchaseIosProps {
348
322
  readonly sku: string;
349
323
  readonly andDangerouslyFinishTransactionAutomaticallyIOS?: boolean;
350
324
  readonly appAccountToken?: string;
@@ -355,7 +329,7 @@ export interface IosRequestPurchaseProps {
355
329
  /**
356
330
  * Android-specific purchase request parameters
357
331
  */
358
- export interface AndroidRequestPurchaseProps {
332
+ export interface RequestPurchaseAndroidProps {
359
333
  readonly skus: string[];
360
334
  readonly obfuscatedAccountIdAndroid?: string;
361
335
  readonly obfuscatedProfileIdAndroid?: string;
@@ -365,8 +339,8 @@ export interface AndroidRequestPurchaseProps {
365
339
  /**
366
340
  * Android-specific subscription request parameters
367
341
  */
368
- export interface AndroidRequestSubscriptionProps
369
- extends AndroidRequestPurchaseProps {
342
+ export interface RequestSubscriptionAndroidProps
343
+ extends RequestPurchaseAndroidProps {
370
344
  readonly purchaseTokenAndroid?: string;
371
345
  readonly replacementModeAndroid?: number;
372
346
  readonly subscriptionOffers: {
@@ -379,88 +353,29 @@ export interface AndroidRequestSubscriptionProps
379
353
  * Modern platform-specific request structure (v2.7.0+)
380
354
  * Allows clear separation of iOS and Android parameters
381
355
  */
382
- export interface PlatformRequestPurchaseProps {
383
- readonly ios?: IosRequestPurchaseProps;
384
- readonly android?: AndroidRequestPurchaseProps;
356
+ export interface RequestPurchasePropsByPlatforms {
357
+ readonly ios?: RequestPurchaseIosProps;
358
+ readonly android?: RequestPurchaseAndroidProps;
385
359
  }
386
360
 
387
361
  /**
388
362
  * Modern platform-specific subscription request structure (v2.7.0+)
389
363
  */
390
- export interface PlatformRequestSubscriptionProps {
391
- readonly ios?: IosRequestPurchaseProps;
392
- readonly android?: AndroidRequestSubscriptionProps;
364
+ export interface RequestSubscriptionPropsByPlatforms {
365
+ readonly ios?: RequestPurchaseIosProps;
366
+ readonly android?: RequestSubscriptionAndroidProps;
393
367
  }
394
368
 
395
369
  /**
396
370
  * Modern request purchase parameters (v2.7.0+)
397
371
  * This is the recommended API moving forward
398
372
  */
399
- export type RequestPurchaseProps = PlatformRequestPurchaseProps;
373
+ export type RequestPurchaseProps = RequestPurchasePropsByPlatforms;
400
374
 
401
375
  /**
402
376
  * Modern request subscription parameters (v2.7.0+)
403
377
  * This is the recommended API moving forward
404
378
  */
405
- export type RequestSubscriptionProps = PlatformRequestSubscriptionProps;
406
-
407
- /**
408
- * Legacy request purchase parameters (deprecated)
409
- * Includes both unified and old platform-specific formats
410
- * @deprecated Use RequestPurchaseProps with platform-specific structure instead
411
- */
412
- export type LegacyRequestPurchasePropsAll =
413
- | UnifiedRequestPurchaseProps
414
- | LegacyRequestPurchaseProps;
415
-
416
- /**
417
- * Legacy request subscription parameters (deprecated)
418
- * Includes both unified and old platform-specific formats
419
- * @deprecated Use RequestSubscriptionProps with platform-specific structure instead
420
- */
421
- export type LegacyRequestSubscriptionPropsAll =
422
- | UnifiedRequestSubscriptionProps
423
- | LegacyRequestSubscriptionProps;
424
-
425
- /**
426
- * All supported request purchase parameters
427
- * Used internally for backward compatibility
428
- * @internal
429
- */
430
- export type RequestPurchasePropsWithLegacy =
431
- | RequestPurchaseProps
432
- | LegacyRequestPurchasePropsAll;
433
-
434
- /**
435
- * All supported request subscription parameters
436
- * Used internally for backward compatibility
437
- * @internal
438
- */
439
- export type RequestSubscriptionPropsWithLegacy =
440
- | RequestSubscriptionProps
441
- | LegacyRequestSubscriptionPropsAll;
442
-
443
- // ============================================================================
444
- // Type Guards and Utility Functions
445
- // ============================================================================
446
-
447
- // Type guards to check which API style is being used
448
- export function isPlatformRequestProps(
449
- props: RequestPurchasePropsWithLegacy | RequestSubscriptionPropsWithLegacy,
450
- ): props is PlatformRequestPurchaseProps | PlatformRequestSubscriptionProps {
451
- return 'ios' in props || 'android' in props;
452
- }
453
-
454
- export function isUnifiedRequestProps(
455
- props: RequestPurchasePropsWithLegacy | RequestSubscriptionPropsWithLegacy,
456
- ): props is UnifiedRequestPurchaseProps | UnifiedRequestSubscriptionProps {
457
- return 'sku' in props || 'skus' in props;
458
- }
459
-
460
- export function isLegacyRequestProps(
461
- props: RequestPurchasePropsWithLegacy | RequestSubscriptionPropsWithLegacy,
462
- ): props is LegacyRequestPurchaseProps | LegacyRequestSubscriptionProps {
463
- return 'productId' in props || 'productIds' in props;
464
- }
379
+ export type RequestSubscriptionProps = RequestSubscriptionPropsByPlatforms;
465
380
 
466
- // Note: Other type guard functions are exported from index.ts to avoid conflicts
381
+ // Note: Type guard functions are exported from index.ts to avoid conflicts
@@ -0,0 +1,122 @@
1
+ import {Platform} from 'react-native';
2
+ import {getAvailablePurchases} from '../index';
3
+
4
+ export interface ActiveSubscription {
5
+ productId: string;
6
+ isActive: boolean;
7
+ expirationDateIOS?: Date;
8
+ autoRenewingAndroid?: boolean;
9
+ environmentIOS?: string;
10
+ willExpireSoon?: boolean;
11
+ daysUntilExpirationIOS?: number;
12
+ }
13
+
14
+ /**
15
+ * Get all active subscriptions with detailed information
16
+ * @param subscriptionIds - Optional array of subscription product IDs to filter. If not provided, returns all active subscriptions.
17
+ * @returns Promise<ActiveSubscription[]> array of active subscriptions with details
18
+ */
19
+ export const getActiveSubscriptions = async (
20
+ subscriptionIds?: string[],
21
+ ): Promise<ActiveSubscription[]> => {
22
+ try {
23
+ const purchases = await getAvailablePurchases();
24
+ const currentTime = Date.now();
25
+ const activeSubscriptions: ActiveSubscription[] = [];
26
+
27
+ // Filter purchases to find active subscriptions
28
+ const filteredPurchases = purchases.filter((purchase) => {
29
+ // If specific IDs provided, filter by them
30
+ if (subscriptionIds && subscriptionIds.length > 0) {
31
+ if (!subscriptionIds.includes(purchase.id)) {
32
+ return false;
33
+ }
34
+ }
35
+
36
+ // Check if this purchase has subscription-specific fields
37
+ const hasSubscriptionFields =
38
+ ('expirationDateIos' in purchase && purchase.expirationDateIos) ||
39
+ 'autoRenewingAndroid' in purchase;
40
+
41
+ if (!hasSubscriptionFields) {
42
+ return false;
43
+ }
44
+
45
+ // Check if it's actually active
46
+ if (Platform.OS === 'ios') {
47
+ if ('expirationDateIos' in purchase && purchase.expirationDateIos) {
48
+ return purchase.expirationDateIos > currentTime;
49
+ }
50
+ if (
51
+ 'environmentIos' in purchase &&
52
+ purchase.environmentIos === 'Sandbox'
53
+ ) {
54
+ const dayInMs = 24 * 60 * 60 * 1000;
55
+ if (
56
+ purchase.transactionDate &&
57
+ currentTime - purchase.transactionDate < dayInMs
58
+ ) {
59
+ return true;
60
+ }
61
+ }
62
+ } else if (Platform.OS === 'android') {
63
+ // For Android, if it's in the purchases list, it's active
64
+ return true;
65
+ }
66
+
67
+ return false;
68
+ });
69
+
70
+ // Convert to ActiveSubscription format
71
+ for (const purchase of filteredPurchases) {
72
+ const subscription: ActiveSubscription = {
73
+ productId: purchase.id,
74
+ isActive: true,
75
+ };
76
+
77
+ // Add platform-specific details
78
+ if (Platform.OS === 'ios') {
79
+ if ('expirationDateIos' in purchase && purchase.expirationDateIos) {
80
+ const expirationDate = new Date(purchase.expirationDateIos);
81
+ subscription.expirationDateIOS = expirationDate;
82
+
83
+ // Calculate days until expiration
84
+ const daysUntilExpiration = Math.floor(
85
+ (purchase.expirationDateIos - currentTime) / (1000 * 60 * 60 * 24),
86
+ );
87
+ subscription.daysUntilExpirationIOS = daysUntilExpiration;
88
+ subscription.willExpireSoon = daysUntilExpiration <= 7;
89
+ }
90
+
91
+ if ('environmentIos' in purchase) {
92
+ subscription.environmentIOS = purchase.environmentIos;
93
+ }
94
+ } else if (Platform.OS === 'android') {
95
+ if ('autoRenewingAndroid' in purchase) {
96
+ subscription.autoRenewingAndroid = purchase.autoRenewingAndroid;
97
+ // If auto-renewing is false, subscription will expire soon
98
+ subscription.willExpireSoon = !purchase.autoRenewingAndroid;
99
+ }
100
+ }
101
+
102
+ activeSubscriptions.push(subscription);
103
+ }
104
+
105
+ return activeSubscriptions;
106
+ } catch (error) {
107
+ console.error('Error getting active subscriptions:', error);
108
+ return [];
109
+ }
110
+ };
111
+
112
+ /**
113
+ * Check if user has any active subscriptions
114
+ * @param subscriptionIds - Optional array of subscription product IDs to check. If not provided, checks all subscriptions.
115
+ * @returns Promise<boolean> true if user has at least one active subscription
116
+ */
117
+ export const hasActiveSubscriptions = async (
118
+ subscriptionIds?: string[],
119
+ ): Promise<boolean> => {
120
+ const subscriptions = await getActiveSubscriptions(subscriptionIds);
121
+ return subscriptions.length > 0;
122
+ };
package/src/index.ts CHANGED
@@ -22,12 +22,10 @@ import {
22
22
  Purchase,
23
23
  PurchaseError,
24
24
  PurchaseResult,
25
- RequestSubscriptionPropsWithLegacy,
26
- RequestPurchasePropsWithLegacy,
25
+ RequestSubscriptionProps,
26
+ RequestPurchaseProps,
27
27
  SubscriptionProduct,
28
28
  SubscriptionPurchase,
29
- isPlatformRequestProps,
30
- isUnifiedRequestProps,
31
29
  } from './ExpoIap.types';
32
30
  import {ProductPurchaseAndroid} from './types/ExpoIapAndroid.types';
33
31
  import {PaymentDiscount} from './types/ExpoIapIos.types';
@@ -38,6 +36,13 @@ export * from './modules/android';
38
36
  export * from './modules/ios';
39
37
  export type {AppTransactionIOS} from './types/ExpoIapIos.types';
40
38
 
39
+ // Export subscription helpers
40
+ export {
41
+ getActiveSubscriptions,
42
+ hasActiveSubscriptions,
43
+ type ActiveSubscription,
44
+ } from './helpers/subscription';
45
+
41
46
  // Get the native constant value
42
47
  export const PI = ExpoIapModule.PI;
43
48
 
@@ -328,8 +333,9 @@ export const getAvailablePurchases = ({
328
333
  ),
329
334
  android: async () => {
330
335
  const products = await ExpoIapModule.getAvailableItemsByType('inapp');
331
- const subscriptions =
332
- await ExpoIapModule.getAvailableItemsByType('subs');
336
+ const subscriptions = await ExpoIapModule.getAvailableItemsByType(
337
+ 'subs',
338
+ );
333
339
  return products.concat(subscriptions);
334
340
  },
335
341
  }) || (() => Promise.resolve([]))
@@ -349,14 +355,13 @@ const offerToRecordIos = (
349
355
  };
350
356
 
351
357
  // Define discriminated union with explicit type parameter
352
- // Using legacy types internally for backward compatibility
353
358
  type PurchaseRequest =
354
359
  | {
355
- request: RequestPurchasePropsWithLegacy;
360
+ request: RequestPurchaseProps;
356
361
  type?: 'inapp';
357
362
  }
358
363
  | {
359
- request: RequestSubscriptionPropsWithLegacy;
364
+ request: RequestSubscriptionProps;
360
365
  type: 'subs';
361
366
  };
362
367
 
@@ -364,50 +369,11 @@ type PurchaseRequest =
364
369
  * Helper to normalize request props to platform-specific format
365
370
  */
366
371
  const normalizeRequestProps = (
367
- request: RequestPurchasePropsWithLegacy | RequestSubscriptionPropsWithLegacy,
372
+ request: RequestPurchaseProps | RequestSubscriptionProps,
368
373
  platform: 'ios' | 'android',
369
374
  ): any => {
370
- // If it's already platform-specific format
371
- if (isPlatformRequestProps(request)) {
372
- return platform === 'ios' ? request.ios : request.android;
373
- }
374
-
375
- // If it's unified format, convert to platform-specific
376
- if (isUnifiedRequestProps(request)) {
377
- if (platform === 'ios') {
378
- return {
379
- sku: request.sku || (request.skus?.[0] ?? ''),
380
- andDangerouslyFinishTransactionAutomaticallyIOS:
381
- request.andDangerouslyFinishTransactionAutomaticallyIOS,
382
- appAccountToken: request.appAccountToken,
383
- quantity: request.quantity,
384
- withOffer: request.withOffer,
385
- };
386
- } else {
387
- const androidRequest: any = {
388
- skus: request.skus || (request.sku ? [request.sku] : []),
389
- obfuscatedAccountIdAndroid: request.obfuscatedAccountIdAndroid,
390
- obfuscatedProfileIdAndroid: request.obfuscatedProfileIdAndroid,
391
- isOfferPersonalized: request.isOfferPersonalized,
392
- };
393
-
394
- // Add subscription-specific fields if present
395
- if ('subscriptionOffers' in request && request.subscriptionOffers) {
396
- androidRequest.subscriptionOffers = request.subscriptionOffers;
397
- }
398
- if ('purchaseTokenAndroid' in request) {
399
- androidRequest.purchaseTokenAndroid = request.purchaseTokenAndroid;
400
- }
401
- if ('replacementModeAndroid' in request) {
402
- androidRequest.replacementModeAndroid = request.replacementModeAndroid;
403
- }
404
-
405
- return androidRequest;
406
- }
407
- }
408
-
409
- // Legacy format handling
410
- return request;
375
+ // Platform-specific format - directly return the appropriate platform data
376
+ return platform === 'ios' ? request.ios : request.android;
411
377
  };
412
378
 
413
379
  /**
@@ -575,7 +541,7 @@ export const requestPurchase = (
575
541
  * ```
576
542
  */
577
543
  export const requestSubscription = async (
578
- request: RequestSubscriptionPropsWithLegacy,
544
+ request: RequestSubscriptionProps,
579
545
  ): Promise<SubscriptionPurchase | SubscriptionPurchase[] | null | void> => {
580
546
  console.warn(
581
547
  "`requestSubscription` is deprecated and will be removed in version 3.0.0. Use `requestPurchase({ request, type: 'subs' })` instead.",
@@ -597,10 +563,10 @@ export const finishTransaction = ({
597
563
  return (
598
564
  Platform.select({
599
565
  ios: async () => {
600
- const transactionId = purchase.transactionId;
566
+ const transactionId = purchase.id;
601
567
  if (!transactionId) {
602
568
  return Promise.reject(
603
- new Error('transactionId required to finish iOS transaction'),
569
+ new Error('purchase.id required to finish iOS transaction'),
604
570
  );
605
571
  }
606
572
  await ExpoIapModule.finishTransaction(transactionId);
package/src/useIap.ts CHANGED
@@ -16,6 +16,9 @@ import {
16
16
  requestPurchase as requestPurchaseInternal,
17
17
  requestProducts,
18
18
  validateReceipt as validateReceiptInternal,
19
+ getActiveSubscriptions,
20
+ hasActiveSubscriptions,
21
+ type ActiveSubscription,
19
22
  } from './';
20
23
  import {
21
24
  syncIOS,
@@ -47,6 +50,7 @@ type UseIap = {
47
50
  currentPurchase?: ProductPurchase;
48
51
  currentPurchaseError?: PurchaseError;
49
52
  promotedProductIOS?: Product;
53
+ activeSubscriptions: ActiveSubscription[];
50
54
  clearCurrentPurchase: () => void;
51
55
  clearCurrentPurchaseError: () => void;
52
56
  finishTransaction: ({
@@ -88,6 +92,10 @@ type UseIap = {
88
92
  restorePurchases: () => Promise<void>; // 구매 복원 함수 추가
89
93
  getPromotedProductIOS: () => Promise<any | null>;
90
94
  buyPromotedProductIOS: () => Promise<void>;
95
+ getActiveSubscriptions: (
96
+ subscriptionIds?: string[],
97
+ ) => Promise<ActiveSubscription[]>;
98
+ hasActiveSubscriptions: (subscriptionIds?: string[]) => Promise<boolean>;
91
99
  };
92
100
 
93
101
  export interface UseIAPOptions {
@@ -116,6 +124,9 @@ export function useIAP(options?: UseIAPOptions): UseIap {
116
124
  const [currentPurchaseError, setCurrentPurchaseError] =
117
125
  useState<PurchaseError>();
118
126
  const [promotedProductIdIOS] = useState<string>();
127
+ const [activeSubscriptions, setActiveSubscriptions] = useState<
128
+ ActiveSubscription[]
129
+ >([]);
119
130
 
120
131
  const optionsRef = useRef<UseIAPOptions | undefined>(options);
121
132
 
@@ -241,6 +252,33 @@ export function useIAP(options?: UseIAPOptions): UseIap {
241
252
  }
242
253
  }, []);
243
254
 
255
+ const getActiveSubscriptionsInternal = useCallback(
256
+ async (subscriptionIds?: string[]): Promise<ActiveSubscription[]> => {
257
+ try {
258
+ const result = await getActiveSubscriptions(subscriptionIds);
259
+ setActiveSubscriptions(result);
260
+ return result;
261
+ } catch (error) {
262
+ console.error('Error getting active subscriptions:', error);
263
+ setActiveSubscriptions([]);
264
+ return [];
265
+ }
266
+ },
267
+ [],
268
+ );
269
+
270
+ const hasActiveSubscriptionsInternal = useCallback(
271
+ async (subscriptionIds?: string[]): Promise<boolean> => {
272
+ try {
273
+ return await hasActiveSubscriptions(subscriptionIds);
274
+ } catch (error) {
275
+ console.error('Error checking active subscriptions:', error);
276
+ return false;
277
+ }
278
+ },
279
+ [],
280
+ );
281
+
244
282
  const getPurchaseHistoriesInternal = useCallback(async (): Promise<void> => {
245
283
  setPurchaseHistories(await getPurchaseHistories());
246
284
  }, []);
@@ -408,6 +446,7 @@ export function useIAP(options?: UseIAPOptions): UseIap {
408
446
  currentPurchase,
409
447
  currentPurchaseError,
410
448
  promotedProductIOS,
449
+ activeSubscriptions,
411
450
  clearCurrentPurchase,
412
451
  clearCurrentPurchaseError,
413
452
  getAvailablePurchases: getAvailablePurchasesInternal,
@@ -420,5 +459,7 @@ export function useIAP(options?: UseIAPOptions): UseIap {
420
459
  getSubscriptions: getSubscriptionsInternal,
421
460
  getPromotedProductIOS,
422
461
  buyPromotedProductIOS,
462
+ getActiveSubscriptions: getActiveSubscriptionsInternal,
463
+ hasActiveSubscriptions: hasActiveSubscriptionsInternal,
423
464
  };
424
465
  }
package/bun.lockb DELETED
Binary file