expo-iap 2.7.11 → 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.11",
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"}
@@ -28,8 +28,9 @@ export type ProductBase = {
28
28
  };
29
29
 
30
30
  export type PurchaseBase = {
31
- id: string;
32
- 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
33
34
  transactionDate: number;
34
35
  transactionReceipt: string;
35
36
  };
@@ -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
@@ -36,6 +36,13 @@ export * from './modules/android';
36
36
  export * from './modules/ios';
37
37
  export type {AppTransactionIOS} from './types/ExpoIapIos.types';
38
38
 
39
+ // Export subscription helpers
40
+ export {
41
+ getActiveSubscriptions,
42
+ hasActiveSubscriptions,
43
+ type ActiveSubscription,
44
+ } from './helpers/subscription';
45
+
39
46
  // Get the native constant value
40
47
  export const PI = ExpoIapModule.PI;
41
48
 
@@ -326,8 +333,9 @@ export const getAvailablePurchases = ({
326
333
  ),
327
334
  android: async () => {
328
335
  const products = await ExpoIapModule.getAvailableItemsByType('inapp');
329
- const subscriptions =
330
- await ExpoIapModule.getAvailableItemsByType('subs');
336
+ const subscriptions = await ExpoIapModule.getAvailableItemsByType(
337
+ 'subs',
338
+ );
331
339
  return products.concat(subscriptions);
332
340
  },
333
341
  }) || (() => Promise.resolve([]))
@@ -555,10 +563,10 @@ export const finishTransaction = ({
555
563
  return (
556
564
  Platform.select({
557
565
  ios: async () => {
558
- const transactionId = purchase.transactionId;
566
+ const transactionId = purchase.id;
559
567
  if (!transactionId) {
560
568
  return Promise.reject(
561
- new Error('transactionId required to finish iOS transaction'),
569
+ new Error('purchase.id required to finish iOS transaction'),
562
570
  );
563
571
  }
564
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