expo-iap 3.1.14 → 3.1.16
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/android/src/main/java/expo/modules/iap/ExpoIapModule.kt +35 -0
- package/build/index.d.ts +45 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +50 -2
- package/build/index.js.map +1 -1
- package/build/types.d.ts +47 -0
- package/build/types.d.ts.map +1 -1
- package/build/types.js.map +1 -1
- package/bun.lockb +0 -0
- package/coverage/clover.xml +182 -241
- package/coverage/coverage-final.json +2 -3
- package/coverage/lcov-report/index.html +25 -40
- package/coverage/lcov-report/src/index.html +18 -18
- package/coverage/lcov-report/src/index.ts.html +186 -21
- package/coverage/lcov-report/src/modules/android.ts.html +1 -1
- package/coverage/lcov-report/src/modules/index.html +1 -1
- package/coverage/lcov-report/src/modules/ios.ts.html +1 -1
- package/coverage/lcov-report/src/utils/debug.ts.html +13 -13
- package/coverage/lcov-report/src/utils/errorMapping.ts.html +1 -1
- package/coverage/lcov-report/src/utils/index.html +15 -15
- package/coverage/lcov.info +340 -445
- package/expo-module.config.json +10 -3
- package/ios/ExpoIapModule.swift +17 -0
- package/openiap-versions.json +3 -3
- package/package.json +1 -1
- package/src/index.ts +61 -6
- package/src/types.ts +47 -0
- package/build/helpers/subscription.d.ts +0 -14
- package/build/helpers/subscription.d.ts.map +0 -1
- package/build/helpers/subscription.js +0 -118
- package/build/helpers/subscription.js.map +0 -1
- package/coverage/lcov-report/src/helpers/index.html +0 -116
- package/coverage/lcov-report/src/helpers/subscription.ts.html +0 -499
- package/src/helpers/subscription.ts +0 -138
package/expo-module.config.json
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
{
|
|
2
|
-
"platforms": [
|
|
2
|
+
"platforms": [
|
|
3
|
+
"ios",
|
|
4
|
+
"android"
|
|
5
|
+
],
|
|
3
6
|
"ios": {
|
|
4
|
-
"modules": [
|
|
7
|
+
"modules": [
|
|
8
|
+
"ExpoIapModule"
|
|
9
|
+
]
|
|
5
10
|
},
|
|
6
11
|
"android": {
|
|
7
|
-
"modules": [
|
|
12
|
+
"modules": [
|
|
13
|
+
"expo.modules.iap.ExpoIapModule"
|
|
14
|
+
]
|
|
8
15
|
}
|
|
9
16
|
}
|
package/ios/ExpoIapModule.swift
CHANGED
|
@@ -339,6 +339,23 @@ public final class ExpoIapModule: Module {
|
|
|
339
339
|
}
|
|
340
340
|
}
|
|
341
341
|
|
|
342
|
+
AsyncFunction("getActiveSubscriptions") { (subscriptionIds: [String]?) async throws -> [[String: Any]] in
|
|
343
|
+
ExpoIapLog.payload("getActiveSubscriptions", payload: subscriptionIds.map { ["subscriptionIds": $0] } ?? [:])
|
|
344
|
+
try await ExpoIapHelper.ensureConnection(isInitialized: self.isInitialized)
|
|
345
|
+
let subscriptions = try await OpenIapModule.shared.getActiveSubscriptions(subscriptionIds)
|
|
346
|
+
let sanitized = subscriptions.map { ExpoIapHelper.sanitizeDictionary(OpenIapSerialization.encode($0)) }
|
|
347
|
+
ExpoIapLog.result("getActiveSubscriptions", value: sanitized)
|
|
348
|
+
return sanitized
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
AsyncFunction("hasActiveSubscriptions") { (subscriptionIds: [String]?) async throws -> Bool in
|
|
352
|
+
ExpoIapLog.payload("hasActiveSubscriptions", payload: subscriptionIds.map { ["subscriptionIds": $0] } ?? [:])
|
|
353
|
+
try await ExpoIapHelper.ensureConnection(isInitialized: self.isInitialized)
|
|
354
|
+
let hasActive = try await OpenIapModule.shared.hasActiveSubscriptions(subscriptionIds)
|
|
355
|
+
ExpoIapLog.result("hasActiveSubscriptions", value: hasActive)
|
|
356
|
+
return hasActive
|
|
357
|
+
}
|
|
358
|
+
|
|
342
359
|
// MARK: - External Purchase (iOS 16.0+)
|
|
343
360
|
|
|
344
361
|
AsyncFunction("canPresentExternalPurchaseNoticeIOS") { () async throws -> Bool in
|
package/openiap-versions.json
CHANGED
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -19,6 +19,7 @@ import {ExpoIapConsole} from './utils/debug';
|
|
|
19
19
|
|
|
20
20
|
// Types
|
|
21
21
|
import type {
|
|
22
|
+
ActiveSubscription,
|
|
22
23
|
AndroidSubscriptionOfferInput,
|
|
23
24
|
DeepLinkOptions,
|
|
24
25
|
FetchProductsResult,
|
|
@@ -47,12 +48,6 @@ export * from './types';
|
|
|
47
48
|
export * from './modules/android';
|
|
48
49
|
export * from './modules/ios';
|
|
49
50
|
|
|
50
|
-
// Export subscription helpers
|
|
51
|
-
export {
|
|
52
|
-
getActiveSubscriptions,
|
|
53
|
-
hasActiveSubscriptions,
|
|
54
|
-
} from './helpers/subscription';
|
|
55
|
-
|
|
56
51
|
// Get the native constant value
|
|
57
52
|
export enum OpenIapEvent {
|
|
58
53
|
PurchaseUpdated = 'purchase-updated',
|
|
@@ -332,6 +327,66 @@ export const getAvailablePurchases: QueryField<
|
|
|
332
327
|
return normalizePurchaseArray(purchases as Purchase[]);
|
|
333
328
|
};
|
|
334
329
|
|
|
330
|
+
/**
|
|
331
|
+
* Get all active subscriptions with detailed information.
|
|
332
|
+
* Uses native OpenIAP module for accurate subscription status and renewal info.
|
|
333
|
+
*
|
|
334
|
+
* On iOS: Returns subscriptions with renewalInfoIOS containing pendingUpgradeProductId,
|
|
335
|
+
* willAutoRenew, autoRenewPreference, and other renewal details.
|
|
336
|
+
*
|
|
337
|
+
* On Android: Filters available purchases to find active subscriptions (fallback implementation).
|
|
338
|
+
*
|
|
339
|
+
* @param subscriptionIds - Optional array of subscription product IDs to filter. If not provided, returns all active subscriptions.
|
|
340
|
+
* @returns Promise resolving to array of active subscriptions with details
|
|
341
|
+
*
|
|
342
|
+
* @example
|
|
343
|
+
* ```typescript
|
|
344
|
+
* // Get all active subscriptions
|
|
345
|
+
* const subs = await getActiveSubscriptions();
|
|
346
|
+
*
|
|
347
|
+
* // Get specific subscriptions
|
|
348
|
+
* const premiumSubs = await getActiveSubscriptions(['premium', 'premium_year']);
|
|
349
|
+
*
|
|
350
|
+
* // Check for pending upgrades (iOS)
|
|
351
|
+
* subs.forEach(sub => {
|
|
352
|
+
* if (sub.renewalInfoIOS?.pendingUpgradeProductId) {
|
|
353
|
+
* console.log(`Upgrade pending to: ${sub.renewalInfoIOS.pendingUpgradeProductId}`);
|
|
354
|
+
* }
|
|
355
|
+
* });
|
|
356
|
+
* ```
|
|
357
|
+
*/
|
|
358
|
+
export const getActiveSubscriptions: QueryField<
|
|
359
|
+
'getActiveSubscriptions'
|
|
360
|
+
> = async (subscriptionIds) => {
|
|
361
|
+
const result = await ExpoIapModule.getActiveSubscriptions(
|
|
362
|
+
subscriptionIds ?? null,
|
|
363
|
+
);
|
|
364
|
+
return (result ?? []) as ActiveSubscription[];
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Check if user has any active subscriptions.
|
|
369
|
+
*
|
|
370
|
+
* @param subscriptionIds - Optional array of subscription product IDs to check. If not provided, checks all subscriptions.
|
|
371
|
+
* @returns Promise resolving to true if user has at least one active subscription
|
|
372
|
+
*
|
|
373
|
+
* @example
|
|
374
|
+
* ```typescript
|
|
375
|
+
* // Check any active subscription
|
|
376
|
+
* const hasAny = await hasActiveSubscriptions();
|
|
377
|
+
*
|
|
378
|
+
* // Check specific subscriptions
|
|
379
|
+
* const hasPremium = await hasActiveSubscriptions(['premium', 'premium_year']);
|
|
380
|
+
* ```
|
|
381
|
+
*/
|
|
382
|
+
export const hasActiveSubscriptions: QueryField<
|
|
383
|
+
'hasActiveSubscriptions'
|
|
384
|
+
> = async (subscriptionIds) => {
|
|
385
|
+
return !!(await ExpoIapModule.hasActiveSubscriptions(
|
|
386
|
+
subscriptionIds ?? null,
|
|
387
|
+
));
|
|
388
|
+
};
|
|
389
|
+
|
|
335
390
|
export const getStorefront: QueryField<'getStorefront'> = async () => {
|
|
336
391
|
if (Platform.OS !== 'ios' && Platform.OS !== 'android') {
|
|
337
392
|
return '';
|
package/src/types.ts
CHANGED
|
@@ -21,6 +21,11 @@ export interface ActiveSubscription {
|
|
|
21
21
|
purchaseToken?: (string | null);
|
|
22
22
|
/** Required for subscription upgrade/downgrade on Android */
|
|
23
23
|
purchaseTokenAndroid?: (string | null);
|
|
24
|
+
/**
|
|
25
|
+
* Renewal information from StoreKit 2 (iOS only). Contains details about subscription renewal status,
|
|
26
|
+
* pending upgrades/downgrades, and auto-renewal preferences.
|
|
27
|
+
*/
|
|
28
|
+
renewalInfoIOS?: (RenewalInfoIOS | null);
|
|
24
29
|
transactionDate: number;
|
|
25
30
|
transactionId: string;
|
|
26
31
|
willExpireSoon?: (boolean | null);
|
|
@@ -487,6 +492,7 @@ export interface PurchaseIOS extends PurchaseCommon {
|
|
|
487
492
|
quantityIOS?: (number | null);
|
|
488
493
|
reasonIOS?: (string | null);
|
|
489
494
|
reasonStringRepresentationIOS?: (string | null);
|
|
495
|
+
renewalInfoIOS?: (RenewalInfoIOS | null);
|
|
490
496
|
revocationDateIOS?: (number | null);
|
|
491
497
|
revocationReasonIOS?: (string | null);
|
|
492
498
|
storefrontCountryCodeIOS?: (string | null);
|
|
@@ -633,9 +639,50 @@ export interface RefundResultIOS {
|
|
|
633
639
|
status: string;
|
|
634
640
|
}
|
|
635
641
|
|
|
642
|
+
/**
|
|
643
|
+
* Subscription renewal information from Product.SubscriptionInfo.RenewalInfo
|
|
644
|
+
* https://developer.apple.com/documentation/storekit/product/subscriptioninfo/renewalinfo
|
|
645
|
+
*/
|
|
636
646
|
export interface RenewalInfoIOS {
|
|
637
647
|
autoRenewPreference?: (string | null);
|
|
648
|
+
/**
|
|
649
|
+
* When subscription expires due to cancellation/billing issue
|
|
650
|
+
* Possible values: "VOLUNTARY", "BILLING_ERROR", "DID_NOT_AGREE_TO_PRICE_INCREASE", "PRODUCT_NOT_AVAILABLE", "UNKNOWN"
|
|
651
|
+
*/
|
|
652
|
+
expirationReason?: (string | null);
|
|
653
|
+
/**
|
|
654
|
+
* Grace period expiration date (milliseconds since epoch)
|
|
655
|
+
* When set, subscription is in grace period (billing issue but still has access)
|
|
656
|
+
*/
|
|
657
|
+
gracePeriodExpirationDate?: (number | null);
|
|
658
|
+
/**
|
|
659
|
+
* True if subscription failed to renew due to billing issue and is retrying
|
|
660
|
+
* Note: Not directly available in RenewalInfo, available in Status
|
|
661
|
+
*/
|
|
662
|
+
isInBillingRetry?: (boolean | null);
|
|
638
663
|
jsonRepresentation?: (string | null);
|
|
664
|
+
/**
|
|
665
|
+
* Product ID that will be used on next renewal (when user upgrades/downgrades)
|
|
666
|
+
* If set and different from current productId, subscription will change on expiration
|
|
667
|
+
*/
|
|
668
|
+
pendingUpgradeProductId?: (string | null);
|
|
669
|
+
/**
|
|
670
|
+
* User's response to subscription price increase
|
|
671
|
+
* Possible values: "AGREED", "PENDING", null (no price increase)
|
|
672
|
+
*/
|
|
673
|
+
priceIncreaseStatus?: (string | null);
|
|
674
|
+
/**
|
|
675
|
+
* Expected renewal date (milliseconds since epoch)
|
|
676
|
+
* For active subscriptions, when the next renewal/charge will occur
|
|
677
|
+
*/
|
|
678
|
+
renewalDate?: (number | null);
|
|
679
|
+
/** Offer ID applied to next renewal (promotional offer, subscription offer code, etc.) */
|
|
680
|
+
renewalOfferId?: (string | null);
|
|
681
|
+
/**
|
|
682
|
+
* Type of offer applied to next renewal
|
|
683
|
+
* Possible values: "PROMOTIONAL", "SUBSCRIPTION_OFFER_CODE", "WIN_BACK", etc.
|
|
684
|
+
*/
|
|
685
|
+
renewalOfferType?: (string | null);
|
|
639
686
|
willAutoRenew: boolean;
|
|
640
687
|
}
|
|
641
688
|
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { ActiveSubscription } from '../types';
|
|
2
|
-
/**
|
|
3
|
-
* Get all active subscriptions with detailed information
|
|
4
|
-
* @param subscriptionIds - Optional array of subscription product IDs to filter. If not provided, returns all active subscriptions.
|
|
5
|
-
* @returns Promise<ActiveSubscription[]> array of active subscriptions with details
|
|
6
|
-
*/
|
|
7
|
-
export declare const getActiveSubscriptions: (subscriptionIds?: string[]) => Promise<ActiveSubscription[]>;
|
|
8
|
-
/**
|
|
9
|
-
* Check if user has any active subscriptions
|
|
10
|
-
* @param subscriptionIds - Optional array of subscription product IDs to check. If not provided, checks all subscriptions.
|
|
11
|
-
* @returns Promise<boolean> true if user has at least one active subscription
|
|
12
|
-
*/
|
|
13
|
-
export declare const hasActiveSubscriptions: (subscriptionIds?: string[]) => Promise<boolean>;
|
|
14
|
-
//# sourceMappingURL=subscription.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"subscription.d.ts","sourceRoot":"","sources":["../../src/helpers/subscription.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,UAAU,CAAC;AAGjD;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,GACjC,kBAAkB,MAAM,EAAE,KACzB,OAAO,CAAC,kBAAkB,EAAE,CAiH9B,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,GACjC,kBAAkB,MAAM,EAAE,KACzB,OAAO,CAAC,OAAO,CAGjB,CAAC"}
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
import { Platform } from 'react-native';
|
|
2
|
-
import { getAvailablePurchases } from '../index';
|
|
3
|
-
import { ExpoIapConsole } from '../utils/debug';
|
|
4
|
-
/**
|
|
5
|
-
* Get all active subscriptions with detailed information
|
|
6
|
-
* @param subscriptionIds - Optional array of subscription product IDs to filter. If not provided, returns all active subscriptions.
|
|
7
|
-
* @returns Promise<ActiveSubscription[]> array of active subscriptions with details
|
|
8
|
-
*/
|
|
9
|
-
export const getActiveSubscriptions = async (subscriptionIds) => {
|
|
10
|
-
try {
|
|
11
|
-
const purchases = await getAvailablePurchases();
|
|
12
|
-
const currentTime = Date.now();
|
|
13
|
-
const activeSubscriptions = [];
|
|
14
|
-
// Filter purchases to find active subscriptions
|
|
15
|
-
const filteredPurchases = purchases.filter((purchase) => {
|
|
16
|
-
// If specific IDs provided, filter by them
|
|
17
|
-
if (subscriptionIds && subscriptionIds.length > 0) {
|
|
18
|
-
if (!subscriptionIds.includes(purchase.productId)) {
|
|
19
|
-
return false;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
// Check if this purchase has subscription-specific fields
|
|
23
|
-
const hasSubscriptionFields = ('expirationDateIOS' in purchase && !!purchase.expirationDateIOS) ||
|
|
24
|
-
'autoRenewingAndroid' in purchase ||
|
|
25
|
-
('environmentIOS' in purchase && !!purchase.environmentIOS);
|
|
26
|
-
if (!hasSubscriptionFields) {
|
|
27
|
-
return false;
|
|
28
|
-
}
|
|
29
|
-
// Check if it's actually active
|
|
30
|
-
if (Platform.OS === 'ios') {
|
|
31
|
-
if ('expirationDateIOS' in purchase && purchase.expirationDateIOS) {
|
|
32
|
-
return purchase.expirationDateIOS > currentTime;
|
|
33
|
-
}
|
|
34
|
-
// For iOS purchases without expiration date (like Sandbox), we consider them active
|
|
35
|
-
// if they have the environmentIOS field and were created recently
|
|
36
|
-
if ('environmentIOS' in purchase && purchase.environmentIOS) {
|
|
37
|
-
const dayInMs = 24 * 60 * 60 * 1000;
|
|
38
|
-
// If no expiration date, consider active if transaction is recent (within 24 hours for Sandbox)
|
|
39
|
-
if (!('expirationDateIOS' in purchase) ||
|
|
40
|
-
!purchase.expirationDateIOS) {
|
|
41
|
-
if (purchase.environmentIOS === 'Sandbox' &&
|
|
42
|
-
purchase.transactionDate &&
|
|
43
|
-
currentTime - purchase.transactionDate < dayInMs) {
|
|
44
|
-
return true;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
else if (Platform.OS === 'android') {
|
|
50
|
-
// For Android, if it's in the purchases list, it's active
|
|
51
|
-
return true;
|
|
52
|
-
}
|
|
53
|
-
return false;
|
|
54
|
-
});
|
|
55
|
-
// Deduplicate by transaction identifier (id)
|
|
56
|
-
const seen = new Set();
|
|
57
|
-
const dedupedPurchases = filteredPurchases.filter((p) => {
|
|
58
|
-
const key = String(p.id);
|
|
59
|
-
if (seen.has(key))
|
|
60
|
-
return false;
|
|
61
|
-
seen.add(key);
|
|
62
|
-
return true;
|
|
63
|
-
});
|
|
64
|
-
// Convert to ActiveSubscription format
|
|
65
|
-
for (const purchase of dedupedPurchases) {
|
|
66
|
-
const subscription = {
|
|
67
|
-
productId: purchase.productId,
|
|
68
|
-
isActive: true,
|
|
69
|
-
transactionId: String(purchase.id),
|
|
70
|
-
purchaseToken: purchase.purchaseToken,
|
|
71
|
-
transactionDate: purchase.transactionDate,
|
|
72
|
-
};
|
|
73
|
-
// Add platform-specific details
|
|
74
|
-
if (Platform.OS === 'ios') {
|
|
75
|
-
if ('expirationDateIOS' in purchase && purchase.expirationDateIOS) {
|
|
76
|
-
subscription.expirationDateIOS = purchase.expirationDateIOS;
|
|
77
|
-
// Calculate days until expiration (round to nearest day)
|
|
78
|
-
const daysUntilExpiration = Math.round((purchase.expirationDateIOS - currentTime) / (1000 * 60 * 60 * 24));
|
|
79
|
-
subscription.daysUntilExpirationIOS = daysUntilExpiration;
|
|
80
|
-
subscription.willExpireSoon = daysUntilExpiration <= 7;
|
|
81
|
-
}
|
|
82
|
-
if ('environmentIOS' in purchase) {
|
|
83
|
-
subscription.environmentIOS = purchase.environmentIOS ?? undefined;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
else if (Platform.OS === 'android') {
|
|
87
|
-
if ('autoRenewingAndroid' in purchase) {
|
|
88
|
-
if (typeof purchase.autoRenewingAndroid !== 'undefined') {
|
|
89
|
-
subscription.autoRenewingAndroid = purchase.autoRenewingAndroid;
|
|
90
|
-
}
|
|
91
|
-
// If auto-renewing is false, subscription will expire soon
|
|
92
|
-
if (purchase.autoRenewingAndroid === false) {
|
|
93
|
-
subscription.willExpireSoon = true;
|
|
94
|
-
}
|
|
95
|
-
else if (purchase.autoRenewingAndroid === true) {
|
|
96
|
-
subscription.willExpireSoon = false;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
activeSubscriptions.push(subscription);
|
|
101
|
-
}
|
|
102
|
-
return activeSubscriptions;
|
|
103
|
-
}
|
|
104
|
-
catch (error) {
|
|
105
|
-
ExpoIapConsole.error('Error getting active subscriptions:', error);
|
|
106
|
-
return [];
|
|
107
|
-
}
|
|
108
|
-
};
|
|
109
|
-
/**
|
|
110
|
-
* Check if user has any active subscriptions
|
|
111
|
-
* @param subscriptionIds - Optional array of subscription product IDs to check. If not provided, checks all subscriptions.
|
|
112
|
-
* @returns Promise<boolean> true if user has at least one active subscription
|
|
113
|
-
*/
|
|
114
|
-
export const hasActiveSubscriptions = async (subscriptionIds) => {
|
|
115
|
-
const subscriptions = await getActiveSubscriptions(subscriptionIds);
|
|
116
|
-
return subscriptions.length > 0;
|
|
117
|
-
};
|
|
118
|
-
//# sourceMappingURL=subscription.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"subscription.js","sourceRoot":"","sources":["../../src/helpers/subscription.ts"],"names":[],"mappings":"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","sourcesContent":["import {Platform} from 'react-native';\nimport {getAvailablePurchases} from '../index';\nimport type {ActiveSubscription} from '../types';\nimport {ExpoIapConsole} from '../utils/debug';\n\n/**\n * Get all active subscriptions with detailed information\n * @param subscriptionIds - Optional array of subscription product IDs to filter. If not provided, returns all active subscriptions.\n * @returns Promise<ActiveSubscription[]> array of active subscriptions with details\n */\nexport const getActiveSubscriptions = async (\n subscriptionIds?: string[],\n): Promise<ActiveSubscription[]> => {\n try {\n const purchases = await getAvailablePurchases();\n const currentTime = Date.now();\n const activeSubscriptions: ActiveSubscription[] = [];\n\n // Filter purchases to find active subscriptions\n const filteredPurchases = purchases.filter((purchase) => {\n // If specific IDs provided, filter by them\n if (subscriptionIds && subscriptionIds.length > 0) {\n if (!subscriptionIds.includes(purchase.productId)) {\n return false;\n }\n }\n\n // Check if this purchase has subscription-specific fields\n const hasSubscriptionFields =\n ('expirationDateIOS' in purchase && !!purchase.expirationDateIOS) ||\n 'autoRenewingAndroid' in purchase ||\n ('environmentIOS' in purchase && !!(purchase as any).environmentIOS);\n\n if (!hasSubscriptionFields) {\n return false;\n }\n\n // Check if it's actually active\n if (Platform.OS === 'ios') {\n if ('expirationDateIOS' in purchase && purchase.expirationDateIOS) {\n return purchase.expirationDateIOS > currentTime;\n }\n // For iOS purchases without expiration date (like Sandbox), we consider them active\n // if they have the environmentIOS field and were created recently\n if ('environmentIOS' in purchase && purchase.environmentIOS) {\n const dayInMs = 24 * 60 * 60 * 1000;\n // If no expiration date, consider active if transaction is recent (within 24 hours for Sandbox)\n if (\n !('expirationDateIOS' in purchase) ||\n !purchase.expirationDateIOS\n ) {\n if (\n purchase.environmentIOS === 'Sandbox' &&\n purchase.transactionDate &&\n currentTime - purchase.transactionDate < dayInMs\n ) {\n return true;\n }\n }\n }\n } else if (Platform.OS === 'android') {\n // For Android, if it's in the purchases list, it's active\n return true;\n }\n\n return false;\n });\n\n // Deduplicate by transaction identifier (id)\n const seen = new Set<string>();\n const dedupedPurchases = filteredPurchases.filter((p) => {\n const key = String(p.id);\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n\n // Convert to ActiveSubscription format\n for (const purchase of dedupedPurchases) {\n const subscription: ActiveSubscription = {\n productId: purchase.productId,\n isActive: true,\n transactionId: String(purchase.id),\n purchaseToken: purchase.purchaseToken,\n transactionDate: purchase.transactionDate,\n };\n\n // Add platform-specific details\n if (Platform.OS === 'ios') {\n if ('expirationDateIOS' in purchase && purchase.expirationDateIOS) {\n subscription.expirationDateIOS = purchase.expirationDateIOS;\n\n // Calculate days until expiration (round to nearest day)\n const daysUntilExpiration = Math.round(\n (purchase.expirationDateIOS - currentTime) / (1000 * 60 * 60 * 24),\n );\n subscription.daysUntilExpirationIOS = daysUntilExpiration;\n subscription.willExpireSoon = daysUntilExpiration <= 7;\n }\n\n if ('environmentIOS' in purchase) {\n subscription.environmentIOS = purchase.environmentIOS ?? undefined;\n }\n } else if (Platform.OS === 'android') {\n if ('autoRenewingAndroid' in purchase) {\n if (typeof purchase.autoRenewingAndroid !== 'undefined') {\n subscription.autoRenewingAndroid = purchase.autoRenewingAndroid;\n }\n // If auto-renewing is false, subscription will expire soon\n if (purchase.autoRenewingAndroid === false) {\n subscription.willExpireSoon = true;\n } else if (purchase.autoRenewingAndroid === true) {\n subscription.willExpireSoon = false;\n }\n }\n }\n\n activeSubscriptions.push(subscription);\n }\n\n return activeSubscriptions;\n } catch (error) {\n ExpoIapConsole.error('Error getting active subscriptions:', error);\n return [];\n }\n};\n\n/**\n * Check if user has any active subscriptions\n * @param subscriptionIds - Optional array of subscription product IDs to check. If not provided, checks all subscriptions.\n * @returns Promise<boolean> true if user has at least one active subscription\n */\nexport const hasActiveSubscriptions = async (\n subscriptionIds?: string[],\n): Promise<boolean> => {\n const subscriptions = await getActiveSubscriptions(subscriptionIds);\n return subscriptions.length > 0;\n};\n"]}
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
<!doctype html>
|
|
3
|
-
<html lang="en">
|
|
4
|
-
|
|
5
|
-
<head>
|
|
6
|
-
<title>Code coverage report for src/helpers</title>
|
|
7
|
-
<meta charset="utf-8" />
|
|
8
|
-
<link rel="stylesheet" href="../../prettify.css" />
|
|
9
|
-
<link rel="stylesheet" href="../../base.css" />
|
|
10
|
-
<link rel="shortcut icon" type="image/x-icon" href="../../favicon.png" />
|
|
11
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
12
|
-
<style type='text/css'>
|
|
13
|
-
.coverage-summary .sorter {
|
|
14
|
-
background-image: url(../../sort-arrow-sprite.png);
|
|
15
|
-
}
|
|
16
|
-
</style>
|
|
17
|
-
</head>
|
|
18
|
-
|
|
19
|
-
<body>
|
|
20
|
-
<div class='wrapper'>
|
|
21
|
-
<div class='pad1'>
|
|
22
|
-
<h1><a href="../../index.html">All files</a> src/helpers</h1>
|
|
23
|
-
<div class='clearfix'>
|
|
24
|
-
|
|
25
|
-
<div class='fl pad1y space-right2'>
|
|
26
|
-
<span class="strong">96.66% </span>
|
|
27
|
-
<span class="quiet">Statements</span>
|
|
28
|
-
<span class='fraction'>58/60</span>
|
|
29
|
-
</div>
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
<div class='fl pad1y space-right2'>
|
|
33
|
-
<span class="strong">92.68% </span>
|
|
34
|
-
<span class="quiet">Branches</span>
|
|
35
|
-
<span class='fraction'>38/41</span>
|
|
36
|
-
</div>
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
<div class='fl pad1y space-right2'>
|
|
40
|
-
<span class="strong">100% </span>
|
|
41
|
-
<span class="quiet">Functions</span>
|
|
42
|
-
<span class='fraction'>4/4</span>
|
|
43
|
-
</div>
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
<div class='fl pad1y space-right2'>
|
|
47
|
-
<span class="strong">98.24% </span>
|
|
48
|
-
<span class="quiet">Lines</span>
|
|
49
|
-
<span class='fraction'>56/57</span>
|
|
50
|
-
</div>
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
</div>
|
|
54
|
-
<p class="quiet">
|
|
55
|
-
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
|
56
|
-
</p>
|
|
57
|
-
<template id="filterTemplate">
|
|
58
|
-
<div class="quiet">
|
|
59
|
-
Filter:
|
|
60
|
-
<input type="search" id="fileSearch">
|
|
61
|
-
</div>
|
|
62
|
-
</template>
|
|
63
|
-
</div>
|
|
64
|
-
<div class='status-line high'></div>
|
|
65
|
-
<div class="pad1">
|
|
66
|
-
<table class="coverage-summary">
|
|
67
|
-
<thead>
|
|
68
|
-
<tr>
|
|
69
|
-
<th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
|
|
70
|
-
<th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
|
|
71
|
-
<th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
|
|
72
|
-
<th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
|
|
73
|
-
<th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
|
|
74
|
-
<th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
|
|
75
|
-
<th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
|
|
76
|
-
<th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
|
|
77
|
-
<th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
|
|
78
|
-
<th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
|
|
79
|
-
</tr>
|
|
80
|
-
</thead>
|
|
81
|
-
<tbody><tr>
|
|
82
|
-
<td class="file high" data-value="subscription.ts"><a href="subscription.ts.html">subscription.ts</a></td>
|
|
83
|
-
<td data-value="96.66" class="pic high">
|
|
84
|
-
<div class="chart"><div class="cover-fill" style="width: 96%"></div><div class="cover-empty" style="width: 4%"></div></div>
|
|
85
|
-
</td>
|
|
86
|
-
<td data-value="96.66" class="pct high">96.66%</td>
|
|
87
|
-
<td data-value="60" class="abs high">58/60</td>
|
|
88
|
-
<td data-value="92.68" class="pct high">92.68%</td>
|
|
89
|
-
<td data-value="41" class="abs high">38/41</td>
|
|
90
|
-
<td data-value="100" class="pct high">100%</td>
|
|
91
|
-
<td data-value="4" class="abs high">4/4</td>
|
|
92
|
-
<td data-value="98.24" class="pct high">98.24%</td>
|
|
93
|
-
<td data-value="57" class="abs high">56/57</td>
|
|
94
|
-
</tr>
|
|
95
|
-
|
|
96
|
-
</tbody>
|
|
97
|
-
</table>
|
|
98
|
-
</div>
|
|
99
|
-
<div class='push'></div><!-- for sticky footer -->
|
|
100
|
-
</div><!-- /wrapper -->
|
|
101
|
-
<div class='footer quiet pad2 space-top1 center small'>
|
|
102
|
-
Code coverage generated by
|
|
103
|
-
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
|
104
|
-
at 2025-10-13T21:06:21.578Z
|
|
105
|
-
</div>
|
|
106
|
-
<script src="../../prettify.js"></script>
|
|
107
|
-
<script>
|
|
108
|
-
window.onload = function () {
|
|
109
|
-
prettyPrint();
|
|
110
|
-
};
|
|
111
|
-
</script>
|
|
112
|
-
<script src="../../sorter.js"></script>
|
|
113
|
-
<script src="../../block-navigation.js"></script>
|
|
114
|
-
</body>
|
|
115
|
-
</html>
|
|
116
|
-
|