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.
- package/CHANGELOG.md +2 -137
- package/CLAUDE.md +55 -0
- package/CONTRIBUTING.md +118 -72
- package/README.md +18 -15
- package/android/src/main/java/expo/modules/iap/ExpoIapModule.kt +6 -5
- package/build/ExpoIap.types.d.ts +14 -53
- package/build/ExpoIap.types.d.ts.map +1 -1
- package/build/ExpoIap.types.js +1 -14
- 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 +5 -4
- package/build/index.d.ts.map +1 -1
- package/build/index.js +6 -41
- 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 +16 -101
- package/src/helpers/subscription.ts +122 -0
- package/src/index.ts +20 -54
- 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
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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
|
|
369
|
-
extends
|
|
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
|
|
383
|
-
readonly ios?:
|
|
384
|
-
readonly android?:
|
|
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
|
|
391
|
-
readonly ios?:
|
|
392
|
-
readonly android?:
|
|
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 =
|
|
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 =
|
|
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:
|
|
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
|
-
|
|
26
|
-
|
|
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
|
-
|
|
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:
|
|
360
|
+
request: RequestPurchaseProps;
|
|
356
361
|
type?: 'inapp';
|
|
357
362
|
}
|
|
358
363
|
| {
|
|
359
|
-
request:
|
|
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:
|
|
372
|
+
request: RequestPurchaseProps | RequestSubscriptionProps,
|
|
368
373
|
platform: 'ios' | 'android',
|
|
369
374
|
): any => {
|
|
370
|
-
//
|
|
371
|
-
|
|
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:
|
|
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.
|
|
566
|
+
const transactionId = purchase.id;
|
|
601
567
|
if (!transactionId) {
|
|
602
568
|
return Promise.reject(
|
|
603
|
-
new Error('
|
|
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
|