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.
- package/CHANGELOG.md +2 -137
- package/CLAUDE.md +55 -0
- package/CONTRIBUTING.md +118 -72
- package/README.md +4 -4
- package/android/src/main/java/expo/modules/iap/ExpoIapModule.kt +6 -5
- package/build/ExpoIap.types.d.ts +1 -0
- package/build/ExpoIap.types.d.ts.map +1 -1
- package/build/ExpoIap.types.js.map +1 -1
- package/build/helpers/subscription.d.ts +22 -0
- package/build/helpers/subscription.d.ts.map +1 -0
- package/build/helpers/subscription.js +92 -0
- package/build/helpers/subscription.js.map +1 -0
- package/build/index.d.ts +1 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +4 -2
- package/build/index.js.map +1 -1
- package/build/useIap.d.ts +4 -0
- package/build/useIap.d.ts.map +1 -1
- package/build/useIap.js +26 -1
- package/build/useIap.js.map +1 -1
- package/bun.lock +2493 -0
- package/ios/ExpoIapModule.swift +3 -2
- package/package.json +1 -1
- package/plugin/tsconfig.tsbuildinfo +1 -1
- package/src/ExpoIap.types.ts +3 -2
- package/src/helpers/subscription.ts +122 -0
- package/src/index.ts +12 -4
- package/src/useIap.ts +41 -0
- package/bun.lockb +0 -0
package/ios/ExpoIapModule.swift
CHANGED
|
@@ -48,9 +48,10 @@ func serializeTransaction(_ transaction: Transaction, jwsRepresentationIos: Stri
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
var purchaseMap: [String: Any?] = [
|
|
51
|
-
"id": transaction.
|
|
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 +1 @@
|
|
|
1
|
-
{"root":["./src/withIAP.ts"],"version":"5.
|
|
1
|
+
{"root":["./src/withIAP.ts"],"version":"5.9.2"}
|
package/src/ExpoIap.types.ts
CHANGED
|
@@ -28,8 +28,9 @@ export type ProductBase = {
|
|
|
28
28
|
};
|
|
29
29
|
|
|
30
30
|
export type PurchaseBase = {
|
|
31
|
-
id: string;
|
|
32
|
-
|
|
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
|
-
|
|
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.
|
|
566
|
+
const transactionId = purchase.id;
|
|
559
567
|
if (!transactionId) {
|
|
560
568
|
return Promise.reject(
|
|
561
|
-
new Error('
|
|
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
|