@umituz/react-native-subscription 2.35.16 → 2.35.18
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/package.json +1 -1
- package/src/domains/config/utils/planSelectors.ts +1 -1
- package/src/domains/credits/presentation/useCredits.ts +6 -20
- package/src/domains/paywall/hooks/usePaywallActions.ts +2 -82
- package/src/domains/revenuecat/core/customerInfoHelpers.ts +21 -0
- package/src/domains/subscription/application/SubscriptionAuthListener.ts +0 -19
- package/src/domains/subscription/application/initializer/BackgroundInitializer.ts +2 -8
- package/src/domains/subscription/application/statusChangeHandlers.ts +0 -30
- package/src/domains/subscription/constants/thresholds.ts +10 -0
- package/src/domains/subscription/infrastructure/handlers/PurchaseStatusResolver.ts +3 -3
- package/src/domains/subscription/infrastructure/handlers/package-operations/PackageFetcher.ts +0 -19
- package/src/domains/subscription/infrastructure/hooks/customer-info/useCustomerInfo.ts +1 -1
- package/src/domains/subscription/infrastructure/hooks/useInitializeSubscription.ts +2 -4
- package/src/domains/subscription/infrastructure/hooks/usePurchasePackage.ts +0 -44
- package/src/domains/subscription/infrastructure/services/CustomerInfoListenerManager.ts +1 -31
- package/src/domains/subscription/infrastructure/services/OfferingsFetcher.ts +0 -21
- package/src/domains/subscription/infrastructure/services/listeners/CustomerInfoHandler.ts +6 -36
- package/src/domains/subscription/infrastructure/services/purchase/PurchaseExecutor.ts +0 -6
- package/src/domains/subscription/infrastructure/utils/PremiumStatusSyncer.ts +3 -44
- package/src/domains/subscription/presentation/featureGateActions.ts +0 -37
- package/src/domains/subscription/presentation/screens/components/SubscriptionHeader.tsx +1 -1
- package/src/domains/subscription/presentation/screens/components/SubscriptionHeaderContent.tsx +1 -1
- package/src/domains/subscription/presentation/useAuthAwarePurchase.ts +0 -43
- package/src/domains/subscription/presentation/useFeatureGate.ts +0 -39
- package/src/domains/subscription/presentation/useSubscriptionStatus.ts +6 -20
- package/src/domains/subscription/utils/authGuards.ts +26 -2
- package/src/domains/subscription/utils/expirationHelpers.ts +2 -2
- package/src/domains/wallet/presentation/hooks/useProductMetadata.ts +3 -6
- package/src/domains/wallet/presentation/hooks/useTransactionHistory.ts +3 -6
- package/src/shared/infrastructure/react-query/hooks/usePreviousUserCleanup.ts +39 -0
- package/src/shared/infrastructure/react-query/queryConfig.ts +22 -0
- package/src/shared/infrastructure/react-query/queryInvalidation.ts +46 -0
- package/src/shared/presentation/hooks/useServiceCall.ts +2 -1
- package/src/shared/utils/errorUtils.ts +32 -0
- package/src/utils/appUtils.ts +6 -0
- package/src/domains/subscription/presentation/components/details/PremiumDetailsCard.constants.ts +0 -1
- package/src/domains/subscription/presentation/screens/components/SubscriptionHeader.constants.ts +0 -1
|
@@ -4,8 +4,6 @@ export interface OfferingsFetcherDeps {
|
|
|
4
4
|
isInitialized: () => boolean;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
-
declare const __DEV__: boolean;
|
|
8
|
-
|
|
9
7
|
export async function fetchOfferings(deps: OfferingsFetcherDeps): Promise<PurchasesOffering | null> {
|
|
10
8
|
if (!deps.isInitialized()) return null;
|
|
11
9
|
try {
|
|
@@ -21,35 +19,16 @@ export async function fetchOfferings(deps: OfferingsFetcherDeps): Promise<Purcha
|
|
|
21
19
|
}
|
|
22
20
|
|
|
23
21
|
if (offerings.current) {
|
|
24
|
-
if (__DEV__) {
|
|
25
|
-
console.log('[OfferingsFetcher] Using current offering:', {
|
|
26
|
-
id: offerings.current.identifier,
|
|
27
|
-
packagesCount: offerings.current.availablePackages.length,
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
22
|
return offerings.current;
|
|
31
23
|
}
|
|
32
24
|
|
|
33
25
|
const allOfferings = Object.values(offerings.all);
|
|
34
26
|
if (allOfferings.length > 0) {
|
|
35
|
-
if (__DEV__) {
|
|
36
|
-
console.log('[OfferingsFetcher] No current offering, using first from all:', {
|
|
37
|
-
id: allOfferings[0].identifier,
|
|
38
|
-
packagesCount: allOfferings[0].availablePackages.length,
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
27
|
return allOfferings[0];
|
|
42
28
|
}
|
|
43
29
|
|
|
44
|
-
if (__DEV__) {
|
|
45
|
-
console.warn('[OfferingsFetcher] No offerings available!');
|
|
46
|
-
}
|
|
47
|
-
|
|
48
30
|
return null;
|
|
49
31
|
} catch (error) {
|
|
50
|
-
if (__DEV__) {
|
|
51
|
-
console.error('[OfferingsFetcher] Error:', error);
|
|
52
|
-
}
|
|
53
32
|
throw new Error(`Failed to fetch offerings: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
54
33
|
}
|
|
55
34
|
}
|
|
@@ -3,8 +3,6 @@ import type { RevenueCatConfig } from "../../../../revenuecat/core/types";
|
|
|
3
3
|
import { syncPremiumStatus } from "../../utils/PremiumStatusSyncer";
|
|
4
4
|
import { detectRenewal, updateRenewalState, type RenewalState } from "../../utils/renewal";
|
|
5
5
|
|
|
6
|
-
declare const __DEV__: boolean;
|
|
7
|
-
|
|
8
6
|
async function handleRenewal(
|
|
9
7
|
userId: string,
|
|
10
8
|
productId: string,
|
|
@@ -16,12 +14,8 @@ async function handleRenewal(
|
|
|
16
14
|
|
|
17
15
|
try {
|
|
18
16
|
await onRenewalDetected(userId, productId, expirationDate, customerInfo);
|
|
19
|
-
} catch (
|
|
20
|
-
|
|
21
|
-
userId,
|
|
22
|
-
productId,
|
|
23
|
-
error
|
|
24
|
-
});
|
|
17
|
+
} catch (_error) {
|
|
18
|
+
// Callback errors should not break customer info processing
|
|
25
19
|
}
|
|
26
20
|
}
|
|
27
21
|
|
|
@@ -37,14 +31,8 @@ async function handlePlanChange(
|
|
|
37
31
|
|
|
38
32
|
try {
|
|
39
33
|
await onPlanChanged(userId, newProductId, previousProductId, isUpgrade, customerInfo);
|
|
40
|
-
} catch (
|
|
41
|
-
|
|
42
|
-
userId,
|
|
43
|
-
newProductId,
|
|
44
|
-
previousProductId,
|
|
45
|
-
isUpgrade,
|
|
46
|
-
error
|
|
47
|
-
});
|
|
34
|
+
} catch (_error) {
|
|
35
|
+
// Callback errors should not break customer info processing
|
|
48
36
|
}
|
|
49
37
|
}
|
|
50
38
|
|
|
@@ -55,11 +43,8 @@ async function handlePremiumStatusSync(
|
|
|
55
43
|
): Promise<void> {
|
|
56
44
|
try {
|
|
57
45
|
await syncPremiumStatus(config, userId, customerInfo);
|
|
58
|
-
} catch (
|
|
59
|
-
|
|
60
|
-
userId,
|
|
61
|
-
error
|
|
62
|
-
});
|
|
46
|
+
} catch (_error) {
|
|
47
|
+
// Sync errors are logged by PremiumStatusSyncer, don't break processing
|
|
63
48
|
}
|
|
64
49
|
}
|
|
65
50
|
|
|
@@ -84,19 +69,7 @@ export async function processCustomerInfo(
|
|
|
84
69
|
config.entitlementIdentifier
|
|
85
70
|
);
|
|
86
71
|
|
|
87
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
88
|
-
console.log("[CustomerInfoHandler] Renewal detection result:", {
|
|
89
|
-
isRenewal: renewalResult.isRenewal,
|
|
90
|
-
isPlanChange: renewalResult.isPlanChange,
|
|
91
|
-
productId: renewalResult.productId,
|
|
92
|
-
previousProductId: renewalResult.previousProductId,
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
|
|
96
72
|
if (renewalResult.isRenewal) {
|
|
97
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
98
|
-
console.log("[CustomerInfoHandler] Handling renewal");
|
|
99
|
-
}
|
|
100
73
|
await handleRenewal(
|
|
101
74
|
userId,
|
|
102
75
|
renewalResult.productId!,
|
|
@@ -107,9 +80,6 @@ export async function processCustomerInfo(
|
|
|
107
80
|
}
|
|
108
81
|
|
|
109
82
|
if (renewalResult.isPlanChange) {
|
|
110
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
111
|
-
console.log("[CustomerInfoHandler] Handling plan change");
|
|
112
|
-
}
|
|
113
83
|
await handlePlanChange(
|
|
114
84
|
userId,
|
|
115
85
|
renewalResult.productId!,
|
|
@@ -28,8 +28,6 @@ async function executeConsumablePurchase(
|
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
declare const __DEV__: boolean;
|
|
32
|
-
|
|
33
31
|
async function executeSubscriptionPurchase(
|
|
34
32
|
config: RevenueCatConfig,
|
|
35
33
|
userId: string,
|
|
@@ -59,10 +57,6 @@ async function executeSubscriptionPurchase(
|
|
|
59
57
|
|
|
60
58
|
await notifyPurchaseCompleted(config, userId, productId, customerInfo, source, packageType);
|
|
61
59
|
|
|
62
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
63
|
-
console.log("[PurchaseExecutor] Purchase flow completed successfully");
|
|
64
|
-
}
|
|
65
|
-
|
|
66
60
|
return {
|
|
67
61
|
success: true,
|
|
68
62
|
isPremium,
|
|
@@ -8,8 +8,6 @@ import type { RevenueCatConfig, PackageType } from "../../../revenuecat/core/typ
|
|
|
8
8
|
import type { PurchaseSource } from "../../../subscription/core/SubscriptionConstants";
|
|
9
9
|
import { getPremiumEntitlement } from "../../../revenuecat/core/types";
|
|
10
10
|
|
|
11
|
-
declare const __DEV__: boolean;
|
|
12
|
-
|
|
13
11
|
export async function syncPremiumStatus(
|
|
14
12
|
config: RevenueCatConfig,
|
|
15
13
|
userId: string,
|
|
@@ -25,9 +23,6 @@ export async function syncPremiumStatus(
|
|
|
25
23
|
}
|
|
26
24
|
|
|
27
25
|
if (!config.onPremiumStatusChanged) {
|
|
28
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
29
|
-
console.log("[PremiumStatusSyncer] No onPremiumStatusChanged callback - skipping");
|
|
30
|
-
}
|
|
31
26
|
return { success: true };
|
|
32
27
|
}
|
|
33
28
|
|
|
@@ -36,21 +31,8 @@ export async function syncPremiumStatus(
|
|
|
36
31
|
config.entitlementIdentifier
|
|
37
32
|
);
|
|
38
33
|
|
|
39
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
40
|
-
console.log("[PremiumStatusSyncer] Premium entitlement:", {
|
|
41
|
-
found: !!premiumEntitlement,
|
|
42
|
-
productId: premiumEntitlement?.productIdentifier,
|
|
43
|
-
expirationDate: premiumEntitlement?.expirationDate,
|
|
44
|
-
willRenew: premiumEntitlement?.willRenew,
|
|
45
|
-
periodType: premiumEntitlement?.periodType,
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
|
|
49
34
|
try {
|
|
50
35
|
if (premiumEntitlement) {
|
|
51
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
52
|
-
console.log("[PremiumStatusSyncer] Calling onPremiumStatusChanged with premium=true");
|
|
53
|
-
}
|
|
54
36
|
await config.onPremiumStatusChanged(
|
|
55
37
|
userId,
|
|
56
38
|
true,
|
|
@@ -60,22 +42,11 @@ export async function syncPremiumStatus(
|
|
|
60
42
|
premiumEntitlement.periodType as "NORMAL" | "INTRO" | "TRIAL" | undefined
|
|
61
43
|
);
|
|
62
44
|
} else {
|
|
63
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
64
|
-
console.log("[PremiumStatusSyncer] Calling onPremiumStatusChanged with premium=false");
|
|
65
|
-
}
|
|
66
45
|
await config.onPremiumStatusChanged(userId, false, undefined, undefined, undefined, undefined);
|
|
67
46
|
}
|
|
68
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
69
|
-
console.log("[PremiumStatusSyncer] onPremiumStatusChanged completed successfully");
|
|
70
|
-
}
|
|
71
47
|
return { success: true };
|
|
72
48
|
} catch (error) {
|
|
73
|
-
|
|
74
|
-
userId,
|
|
75
|
-
isPremium: !!premiumEntitlement,
|
|
76
|
-
productId: premiumEntitlement?.productIdentifier,
|
|
77
|
-
error
|
|
78
|
-
});
|
|
49
|
+
|
|
79
50
|
return {
|
|
80
51
|
success: false,
|
|
81
52
|
error: error instanceof Error ? error : new Error(String(error))
|
|
@@ -97,14 +68,7 @@ export async function notifyPurchaseCompleted(
|
|
|
97
68
|
|
|
98
69
|
try {
|
|
99
70
|
await config.onPurchaseCompleted(userId, productId, customerInfo, source, packageType);
|
|
100
|
-
} catch (
|
|
101
|
-
console.error('[PremiumStatusSyncer] Purchase completion callback failed', {
|
|
102
|
-
userId,
|
|
103
|
-
productId,
|
|
104
|
-
source,
|
|
105
|
-
packageType,
|
|
106
|
-
error
|
|
107
|
-
});
|
|
71
|
+
} catch (_error) {
|
|
108
72
|
// Silently fail callback notifications to prevent crashing the main flow
|
|
109
73
|
}
|
|
110
74
|
}
|
|
@@ -121,12 +85,7 @@ export async function notifyRestoreCompleted(
|
|
|
121
85
|
|
|
122
86
|
try {
|
|
123
87
|
await config.onRestoreCompleted(userId, isPremium, customerInfo);
|
|
124
|
-
} catch (
|
|
125
|
-
console.error('[PremiumStatusSyncer] Restore completion callback failed', {
|
|
126
|
-
userId,
|
|
127
|
-
isPremium,
|
|
128
|
-
error
|
|
129
|
-
});
|
|
88
|
+
} catch (_error) {
|
|
130
89
|
// Silently fail callback notifications to prevent crashing the main flow
|
|
131
90
|
}
|
|
132
91
|
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import type { MutableRefObject } from "react";
|
|
2
2
|
|
|
3
|
-
declare const __DEV__: boolean;
|
|
4
|
-
|
|
5
3
|
export const executeFeatureAction = (
|
|
6
4
|
action: () => void | Promise<void>,
|
|
7
5
|
isAuthenticated: boolean,
|
|
@@ -15,45 +13,20 @@ export const executeFeatureAction = (
|
|
|
15
13
|
isWaitingForPurchaseRef: MutableRefObject<boolean>,
|
|
16
14
|
isCreditsLoadedRef: MutableRefObject<boolean>
|
|
17
15
|
): boolean => {
|
|
18
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
19
|
-
console.log("[FeatureGate] executeFeatureAction called:", {
|
|
20
|
-
isAuthenticated,
|
|
21
|
-
hasSubscription: hasSubscriptionRef.current,
|
|
22
|
-
creditBalance: creditBalanceRef.current,
|
|
23
|
-
requiredCredits: requiredCreditsRef.current,
|
|
24
|
-
isCreditsLoaded: isCreditsLoadedRef.current,
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
16
|
|
|
28
17
|
if (!isAuthenticated) {
|
|
29
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
30
|
-
console.log("[FeatureGate] User not authenticated, showing auth modal");
|
|
31
|
-
}
|
|
32
18
|
const postAuthAction = () => {
|
|
33
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
34
|
-
console.log("[FeatureGate] Post-auth action called");
|
|
35
|
-
}
|
|
36
19
|
if (hasSubscriptionRef.current || creditBalanceRef.current >= requiredCreditsRef.current) {
|
|
37
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
38
|
-
console.log("[FeatureGate] Post-auth: User has access, executing action");
|
|
39
|
-
}
|
|
40
20
|
action();
|
|
41
21
|
return;
|
|
42
22
|
}
|
|
43
23
|
|
|
44
24
|
if (isCreditsLoadedRef.current) {
|
|
45
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
46
|
-
console.log("[FeatureGate] Post-auth: Credits loaded, showing paywall");
|
|
47
|
-
}
|
|
48
25
|
pendingActionRef.current = action;
|
|
49
26
|
isWaitingForPurchaseRef.current = true;
|
|
50
27
|
onShowPaywallRef.current(requiredCreditsRef.current);
|
|
51
28
|
return;
|
|
52
29
|
}
|
|
53
|
-
|
|
54
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
55
|
-
console.log("[FeatureGate] Post-auth: Waiting for credits to load");
|
|
56
|
-
}
|
|
57
30
|
pendingActionRef.current = action;
|
|
58
31
|
isWaitingForAuthCreditsRef.current = true;
|
|
59
32
|
};
|
|
@@ -62,26 +35,16 @@ export const executeFeatureAction = (
|
|
|
62
35
|
}
|
|
63
36
|
|
|
64
37
|
if (hasSubscriptionRef.current) {
|
|
65
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
66
|
-
console.log("[FeatureGate] User has subscription, executing action");
|
|
67
|
-
}
|
|
68
38
|
action();
|
|
69
39
|
return true;
|
|
70
40
|
}
|
|
71
41
|
|
|
72
42
|
if (creditBalanceRef.current < requiredCreditsRef.current) {
|
|
73
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
74
|
-
console.log("[FeatureGate] Insufficient credits, showing paywall");
|
|
75
|
-
}
|
|
76
43
|
pendingActionRef.current = action;
|
|
77
44
|
isWaitingForPurchaseRef.current = true;
|
|
78
45
|
onShowPaywallRef.current(requiredCreditsRef.current);
|
|
79
46
|
return false;
|
|
80
47
|
}
|
|
81
|
-
|
|
82
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
83
|
-
console.log("[FeatureGate] User has enough credits, executing action");
|
|
84
|
-
}
|
|
85
48
|
action();
|
|
86
49
|
return true;
|
|
87
50
|
};
|
|
@@ -4,7 +4,7 @@ import { useAppDesignTokens, AtomicText } from "@umituz/react-native-design-syst
|
|
|
4
4
|
import { PremiumStatusBadge } from "../../components/details/PremiumStatusBadge";
|
|
5
5
|
import type { SubscriptionHeaderProps } from "./SubscriptionHeader.types";
|
|
6
6
|
import { createSubscriptionHeaderStyles } from "./SubscriptionHeader.styles";
|
|
7
|
-
import { EXPIRING_SOON_THRESHOLD_DAYS } from "
|
|
7
|
+
import { EXPIRATION_WARNING_THRESHOLD_DAYS as EXPIRING_SOON_THRESHOLD_DAYS } from "../../../constants/thresholds";
|
|
8
8
|
import { SubscriptionHeaderContent } from "./SubscriptionHeaderContent";
|
|
9
9
|
|
|
10
10
|
export type { SubscriptionHeaderProps } from "./SubscriptionHeader.types";
|
|
@@ -9,8 +9,6 @@ import { usePremium } from "./usePremium";
|
|
|
9
9
|
import type { PurchaseSource } from "../core/SubscriptionConstants";
|
|
10
10
|
import { authPurchaseStateManager } from "../infrastructure/utils/authPurchaseState";
|
|
11
11
|
|
|
12
|
-
declare const __DEV__: boolean;
|
|
13
|
-
|
|
14
12
|
export type { PurchaseAuthProvider } from "../infrastructure/utils/authPurchaseState";
|
|
15
13
|
|
|
16
14
|
export const configureAuthProvider = (provider: import("../infrastructure/utils/authPurchaseState").PurchaseAuthProvider): void => {
|
|
@@ -46,84 +44,43 @@ export const useAuthAwarePurchase = (
|
|
|
46
44
|
|
|
47
45
|
const handlePurchase = useCallback(
|
|
48
46
|
async (pkg: PurchasesPackage, source?: PurchaseSource): Promise<boolean> => {
|
|
49
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
50
|
-
console.log("[useAuthAwarePurchase] handlePurchase called", {
|
|
51
|
-
productId: pkg.product.identifier,
|
|
52
|
-
source: source || params?.source,
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
47
|
|
|
56
48
|
const authProvider = authPurchaseStateManager.getProvider();
|
|
57
49
|
|
|
58
50
|
if (!authProvider) {
|
|
59
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
60
|
-
console.error("[useAuthAwarePurchase] ❌ No auth provider configured");
|
|
61
|
-
}
|
|
62
51
|
return false;
|
|
63
52
|
}
|
|
64
53
|
|
|
65
54
|
const isAuth = authProvider.isAuthenticated();
|
|
66
55
|
|
|
67
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
68
|
-
console.log("[useAuthAwarePurchase] Auth status:", { isAuth });
|
|
69
|
-
}
|
|
70
|
-
|
|
71
56
|
if (!isAuth) {
|
|
72
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
73
|
-
console.log("[useAuthAwarePurchase] 🔐 User not authenticated, saving purchase and showing auth modal");
|
|
74
|
-
}
|
|
75
57
|
authPurchaseStateManager.savePurchase(pkg, source || params?.source || "settings");
|
|
76
58
|
authProvider.showAuthModal();
|
|
77
59
|
return false;
|
|
78
60
|
}
|
|
79
61
|
|
|
80
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
81
|
-
console.log("[useAuthAwarePurchase] ✅ User authenticated, proceeding with purchase");
|
|
82
|
-
}
|
|
83
|
-
|
|
84
62
|
const result = await purchasePackage(pkg);
|
|
85
63
|
|
|
86
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
87
|
-
console.log("[useAuthAwarePurchase] Purchase result:", result);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
64
|
return result;
|
|
91
65
|
},
|
|
92
66
|
[purchasePackage, params?.source]
|
|
93
67
|
);
|
|
94
68
|
|
|
95
69
|
const handleRestore = useCallback(async (): Promise<boolean> => {
|
|
96
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
97
|
-
console.log("[useAuthAwarePurchase] handleRestore called");
|
|
98
|
-
}
|
|
99
70
|
|
|
100
71
|
const authProvider = authPurchaseStateManager.getProvider();
|
|
101
72
|
|
|
102
73
|
if (!authProvider) {
|
|
103
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
104
|
-
console.error("[useAuthAwarePurchase] ❌ No auth provider configured");
|
|
105
|
-
}
|
|
106
74
|
return false;
|
|
107
75
|
}
|
|
108
76
|
|
|
109
77
|
if (!authProvider.isAuthenticated()) {
|
|
110
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
111
|
-
console.log("[useAuthAwarePurchase] 🔐 User not authenticated, showing auth modal");
|
|
112
|
-
}
|
|
113
78
|
authProvider.showAuthModal();
|
|
114
79
|
return false;
|
|
115
80
|
}
|
|
116
81
|
|
|
117
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
118
|
-
console.log("[useAuthAwarePurchase] ✅ User authenticated, proceeding with restore");
|
|
119
|
-
}
|
|
120
|
-
|
|
121
82
|
const result = await restorePurchase();
|
|
122
83
|
|
|
123
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
124
|
-
console.log("[useAuthAwarePurchase] Restore result:", result);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
84
|
return result;
|
|
128
85
|
}, [restorePurchase]);
|
|
129
86
|
|
|
@@ -6,8 +6,6 @@ import { executeFeatureAction } from "./featureGateActions";
|
|
|
6
6
|
|
|
7
7
|
export type { UseFeatureGateParams, UseFeatureGateResult } from "./useFeatureGate.types";
|
|
8
8
|
|
|
9
|
-
declare const __DEV__: boolean;
|
|
10
|
-
|
|
11
9
|
export function useFeatureGate(params: UseFeatureGateParams): UseFeatureGateResult {
|
|
12
10
|
const {
|
|
13
11
|
isAuthenticated,
|
|
@@ -27,16 +25,6 @@ export function useFeatureGate(params: UseFeatureGateParams): UseFeatureGateResu
|
|
|
27
25
|
const { creditBalanceRef, hasSubscriptionRef, onShowPaywallRef, requiredCreditsRef, isCreditsLoadedRef } = useSyncedRefs(creditBalance, hasSubscription, onShowPaywall, requiredCredits, isCreditsLoaded);
|
|
28
26
|
|
|
29
27
|
useEffect(() => {
|
|
30
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
31
|
-
console.log("[FeatureGate] Auth completion useEffect triggered:", {
|
|
32
|
-
isWaitingForAuthCredits: isWaitingForAuthCreditsRef.current,
|
|
33
|
-
isCreditsLoaded,
|
|
34
|
-
hasPendingAction: !!pendingActionRef.current,
|
|
35
|
-
hasSubscription,
|
|
36
|
-
creditBalance,
|
|
37
|
-
requiredCredits,
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
28
|
|
|
41
29
|
const shouldExecute = canExecuteAuthAction(
|
|
42
30
|
isWaitingForAuthCreditsRef.current,
|
|
@@ -47,14 +35,7 @@ export function useFeatureGate(params: UseFeatureGateParams): UseFeatureGateResu
|
|
|
47
35
|
requiredCredits
|
|
48
36
|
);
|
|
49
37
|
|
|
50
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
51
|
-
console.log("[FeatureGate] canExecuteAuthAction:", shouldExecute);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
38
|
if (shouldExecute) {
|
|
55
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
56
|
-
console.log("[FeatureGate] ✅ EXECUTING PENDING ACTION after auth!");
|
|
57
|
-
}
|
|
58
39
|
isWaitingForAuthCreditsRef.current = false;
|
|
59
40
|
const action = pendingActionRef.current!;
|
|
60
41
|
pendingActionRef.current = null;
|
|
@@ -63,9 +44,6 @@ export function useFeatureGate(params: UseFeatureGateParams): UseFeatureGateResu
|
|
|
63
44
|
}
|
|
64
45
|
|
|
65
46
|
if (isWaitingForAuthCreditsRef.current && isCreditsLoaded && pendingActionRef.current) {
|
|
66
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
67
|
-
console.log("[FeatureGate] Auth credits loaded but insufficient, showing paywall");
|
|
68
|
-
}
|
|
69
47
|
isWaitingForAuthCreditsRef.current = false;
|
|
70
48
|
isWaitingForPurchaseRef.current = true;
|
|
71
49
|
onShowPaywall(requiredCredits);
|
|
@@ -73,16 +51,6 @@ export function useFeatureGate(params: UseFeatureGateParams): UseFeatureGateResu
|
|
|
73
51
|
}, [isCreditsLoaded, creditBalance, hasSubscription, requiredCredits, onShowPaywall]);
|
|
74
52
|
|
|
75
53
|
useEffect(() => {
|
|
76
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
77
|
-
console.log("[FeatureGate] Purchase completion useEffect triggered:", {
|
|
78
|
-
creditBalance,
|
|
79
|
-
prevCreditBalance: prevCreditBalanceRef.current,
|
|
80
|
-
hasSubscription,
|
|
81
|
-
prevHasSubscription: hasSubscriptionRef.current,
|
|
82
|
-
isWaitingForPurchase: isWaitingForPurchaseRef.current,
|
|
83
|
-
hasPendingAction: !!pendingActionRef.current,
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
54
|
|
|
87
55
|
const shouldExecute = canExecutePurchaseAction(
|
|
88
56
|
isWaitingForPurchaseRef.current,
|
|
@@ -93,14 +61,7 @@ export function useFeatureGate(params: UseFeatureGateParams): UseFeatureGateResu
|
|
|
93
61
|
!!pendingActionRef.current
|
|
94
62
|
);
|
|
95
63
|
|
|
96
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
97
|
-
console.log("[FeatureGate] canExecutePurchaseAction:", shouldExecute);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
64
|
if (shouldExecute) {
|
|
101
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
102
|
-
console.log("[FeatureGate] ✅ EXECUTING PENDING ACTION after purchase!");
|
|
103
|
-
}
|
|
104
65
|
const action = pendingActionRef.current!;
|
|
105
66
|
pendingActionRef.current = null;
|
|
106
67
|
isWaitingForPurchaseRef.current = false;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { useQuery, useQueryClient } from "@umituz/react-native-design-system";
|
|
2
|
-
import { useEffect
|
|
2
|
+
import { useEffect } from "react";
|
|
3
3
|
import { useAuthStore, selectUserId } from "@umituz/react-native-auth";
|
|
4
4
|
import { SubscriptionManager } from "../infrastructure/managers/SubscriptionManager";
|
|
5
5
|
import { subscriptionEventBus, SUBSCRIPTION_EVENTS } from "../../../shared/infrastructure/SubscriptionEventBus";
|
|
6
6
|
import { SubscriptionStatusResult } from "./useSubscriptionStatus.types";
|
|
7
7
|
import { isAuthenticated } from "../utils/authGuards";
|
|
8
|
+
import { NO_CACHE_QUERY_CONFIG } from "../../../shared/infrastructure/react-query/queryConfig";
|
|
9
|
+
import { usePreviousUserCleanup } from "../../../shared/infrastructure/react-query/hooks/usePreviousUserCleanup";
|
|
8
10
|
|
|
9
11
|
export const subscriptionStatusQueryKeys = {
|
|
10
12
|
all: ["subscriptionStatus"] as const,
|
|
@@ -38,27 +40,11 @@ export const useSubscriptionStatus = (): SubscriptionStatusResult => {
|
|
|
38
40
|
}
|
|
39
41
|
},
|
|
40
42
|
enabled: queryEnabled,
|
|
41
|
-
|
|
42
|
-
staleTime: 0,
|
|
43
|
-
refetchOnMount: "always",
|
|
44
|
-
refetchOnWindowFocus: "always",
|
|
45
|
-
refetchOnReconnect: "always",
|
|
43
|
+
...NO_CACHE_QUERY_CONFIG,
|
|
46
44
|
});
|
|
47
45
|
|
|
48
|
-
//
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
useEffect(() => {
|
|
52
|
-
const prevUserId = prevUserIdRef.current;
|
|
53
|
-
prevUserIdRef.current = userId;
|
|
54
|
-
|
|
55
|
-
// Clear previous user's cache when userId changes (logout or user switch)
|
|
56
|
-
if (prevUserId !== userId && isAuthenticated(prevUserId)) {
|
|
57
|
-
queryClient.removeQueries({
|
|
58
|
-
queryKey: subscriptionStatusQueryKeys.user(prevUserId),
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
}, [userId, queryClient]);
|
|
46
|
+
// Clean up previous user's cache on logout/user switch
|
|
47
|
+
usePreviousUserCleanup(userId, queryClient, subscriptionStatusQueryKeys.user);
|
|
62
48
|
|
|
63
49
|
useEffect(() => {
|
|
64
50
|
if (!isAuthenticated(userId)) return undefined;
|
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
import { isDefined } from "../../../shared/utils/validators";
|
|
2
2
|
|
|
3
|
-
export
|
|
3
|
+
export function isAuthenticated(userId: string | null | undefined): userId is string {
|
|
4
4
|
return isDefined(userId) && userId.length > 0;
|
|
5
|
-
}
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Requires user to be authenticated, throws if not
|
|
9
|
+
* Type guard that asserts userId is string
|
|
10
|
+
*
|
|
11
|
+
* @param userId - User ID to check
|
|
12
|
+
* @param errorMessage - Custom error message (optional)
|
|
13
|
+
* @throws Error if user is not authenticated
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* function purchaseProduct(userId: string | null) {
|
|
17
|
+
* requireAuthentication(userId); // throws if null/undefined
|
|
18
|
+
* // TypeScript now knows userId is string
|
|
19
|
+
* await purchase(userId);
|
|
20
|
+
* }
|
|
21
|
+
*/
|
|
22
|
+
export function requireAuthentication(
|
|
23
|
+
userId: string | null | undefined,
|
|
24
|
+
errorMessage = "User not authenticated"
|
|
25
|
+
): asserts userId is string {
|
|
26
|
+
if (!isAuthenticated(userId)) {
|
|
27
|
+
throw new Error(errorMessage);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import { EXPIRATION_WARNING_THRESHOLD_DAYS } from '../constants/thresholds';
|
|
2
2
|
|
|
3
3
|
export function shouldHighlightExpiration(daysRemaining: number | null | undefined): boolean {
|
|
4
|
-
return daysRemaining !== null && daysRemaining !== undefined && daysRemaining > 0 && daysRemaining <=
|
|
4
|
+
return daysRemaining !== null && daysRemaining !== undefined && daysRemaining > 0 && daysRemaining <= EXPIRATION_WARNING_THRESHOLD_DAYS;
|
|
5
5
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useQuery } from "@umituz/react-native-design-system";
|
|
2
2
|
import { useMemo } from "react";
|
|
3
|
+
import { NO_CACHE_QUERY_CONFIG } from "../../../../shared/infrastructure/react-query/queryConfig";
|
|
3
4
|
import type {
|
|
4
5
|
ProductMetadata,
|
|
5
6
|
ProductMetadataConfig,
|
|
@@ -34,7 +35,7 @@ export function useProductMetadata({
|
|
|
34
35
|
}: UseProductMetadataParams): UseProductMetadataResult {
|
|
35
36
|
const service = useMemo(
|
|
36
37
|
() => new ProductMetadataService(config),
|
|
37
|
-
[config
|
|
38
|
+
[config]
|
|
38
39
|
);
|
|
39
40
|
|
|
40
41
|
const queryKey = type
|
|
@@ -50,11 +51,7 @@ export function useProductMetadata({
|
|
|
50
51
|
return service.getAll();
|
|
51
52
|
},
|
|
52
53
|
enabled,
|
|
53
|
-
|
|
54
|
-
staleTime: 0,
|
|
55
|
-
refetchOnMount: "always",
|
|
56
|
-
refetchOnWindowFocus: "always",
|
|
57
|
-
refetchOnReconnect: "always",
|
|
54
|
+
...NO_CACHE_QUERY_CONFIG,
|
|
58
55
|
});
|
|
59
56
|
|
|
60
57
|
const products = data ?? [];
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useQuery } from "@umituz/react-native-design-system";
|
|
2
2
|
import { useMemo } from "react";
|
|
3
3
|
import { useAuthStore, selectUserId } from "@umituz/react-native-auth";
|
|
4
|
+
import { NO_CACHE_QUERY_CONFIG } from "../../../../shared/infrastructure/react-query/queryConfig";
|
|
4
5
|
import type {
|
|
5
6
|
CreditLog,
|
|
6
7
|
TransactionRepositoryConfig,
|
|
@@ -33,7 +34,7 @@ export function useTransactionHistory({
|
|
|
33
34
|
|
|
34
35
|
const repository = useMemo(
|
|
35
36
|
() => new TransactionRepository(config),
|
|
36
|
-
[config
|
|
37
|
+
[config]
|
|
37
38
|
);
|
|
38
39
|
|
|
39
40
|
const { data, isLoading, error, refetch } = useQuery({
|
|
@@ -53,11 +54,7 @@ export function useTransactionHistory({
|
|
|
53
54
|
return result.data ?? [];
|
|
54
55
|
},
|
|
55
56
|
enabled: !!userId,
|
|
56
|
-
|
|
57
|
-
staleTime: 0,
|
|
58
|
-
refetchOnMount: "always",
|
|
59
|
-
refetchOnWindowFocus: "always",
|
|
60
|
-
refetchOnReconnect: "always",
|
|
57
|
+
...NO_CACHE_QUERY_CONFIG,
|
|
61
58
|
});
|
|
62
59
|
|
|
63
60
|
const transactions = data ?? [];
|