@umituz/react-native-subscription 2.35.15 → 2.35.17
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 +5 -1
- package/src/domains/credits/presentation/useCredits.ts +6 -5
- 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/SubscriptionSyncProcessor.ts +1 -1
- package/src/domains/subscription/application/initializer/BackgroundInitializer.ts +2 -8
- package/src/domains/subscription/application/initializer/ConfigValidator.ts +2 -2
- package/src/domains/subscription/application/initializer/ServiceConfigurator.ts +23 -3
- 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/usePaywallFlow.ts +12 -2
- package/src/domains/subscription/infrastructure/hooks/usePurchasePackage.ts +0 -44
- package/src/domains/subscription/infrastructure/hooks/useRevenueCat.ts +23 -5
- 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 +1 -40
- 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/trial/application/TrialEligibilityService.ts +1 -1
- package/src/domains/trial/application/TrialService.ts +12 -4
- package/src/domains/wallet/infrastructure/repositories/transaction/TransactionWriter.ts +1 -1
- package/src/domains/wallet/presentation/hooks/useProductMetadata.ts +2 -5
- package/src/domains/wallet/presentation/hooks/useTransactionHistory.ts +2 -5
- 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
|
@@ -19,15 +19,12 @@ import { subscriptionStatusQueryKeys } from "../../presentation/useSubscriptionS
|
|
|
19
19
|
import { creditsQueryKeys } from "../../../credits/presentation/creditsQueryKeys";
|
|
20
20
|
import { getErrorMessage } from "../../../revenuecat/core/errors";
|
|
21
21
|
|
|
22
|
-
declare const __DEV__: boolean;
|
|
23
|
-
|
|
24
22
|
/** Purchase mutation result - simplified for presentation layer */
|
|
25
23
|
export interface PurchaseMutationResult {
|
|
26
24
|
success: boolean;
|
|
27
25
|
productId: string;
|
|
28
26
|
}
|
|
29
27
|
|
|
30
|
-
|
|
31
28
|
/**
|
|
32
29
|
* Purchase a subscription package
|
|
33
30
|
* Credits are initialized by CustomerInfoListener when entitlement becomes active
|
|
@@ -41,26 +38,11 @@ export const usePurchasePackage = () => {
|
|
|
41
38
|
|
|
42
39
|
return useMutation({
|
|
43
40
|
mutationFn: async (pkg: PurchasesPackage): Promise<PurchaseMutationResult> => {
|
|
44
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
45
|
-
console.log("[Purchase] ========================================");
|
|
46
|
-
console.log("[Purchase] mutationFn called:", {
|
|
47
|
-
productId: pkg.product.identifier,
|
|
48
|
-
userId,
|
|
49
|
-
isAnonymous,
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
|
|
53
41
|
if (!userId) {
|
|
54
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
55
|
-
console.log("[Purchase] ERROR: User not authenticated");
|
|
56
|
-
}
|
|
57
42
|
throw new Error("User not authenticated");
|
|
58
43
|
}
|
|
59
44
|
|
|
60
45
|
if (isAnonymous) {
|
|
61
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
62
|
-
console.log("[Purchase] ERROR: Anonymous users cannot purchase");
|
|
63
|
-
}
|
|
64
46
|
throw new Error("Anonymous users cannot purchase subscriptions");
|
|
65
47
|
}
|
|
66
48
|
|
|
@@ -71,21 +53,11 @@ export const usePurchasePackage = () => {
|
|
|
71
53
|
|
|
72
54
|
const success = await SubscriptionManager.purchasePackage(pkg);
|
|
73
55
|
|
|
74
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
75
|
-
console.log("[Purchase] Purchase completed:", { success, productId });
|
|
76
|
-
}
|
|
77
|
-
|
|
78
56
|
return { success, productId };
|
|
79
57
|
},
|
|
80
58
|
onSuccess: (result) => {
|
|
81
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
82
|
-
console.log("[Purchase] onSuccess called:", result);
|
|
83
|
-
}
|
|
84
59
|
|
|
85
60
|
if (result.success) {
|
|
86
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
87
|
-
console.log("[Purchase] ✅ Purchase successful! Invalidating queries...");
|
|
88
|
-
}
|
|
89
61
|
showSuccess("Purchase Successful", "Your subscription is now active!");
|
|
90
62
|
queryClient.invalidateQueries({
|
|
91
63
|
queryKey: SUBSCRIPTION_QUERY_KEYS.packages,
|
|
@@ -97,34 +69,18 @@ export const usePurchasePackage = () => {
|
|
|
97
69
|
queryClient.invalidateQueries({
|
|
98
70
|
queryKey: creditsQueryKeys.user(userId),
|
|
99
71
|
});
|
|
100
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
101
|
-
console.log("[Purchase] Queries invalidated - credits should reload now");
|
|
102
|
-
}
|
|
103
72
|
}
|
|
104
73
|
} else {
|
|
105
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
106
|
-
console.log("[Purchase] ❌ Purchase failed");
|
|
107
|
-
}
|
|
108
74
|
showError("Purchase Failed", "Unable to complete purchase. Please try again.");
|
|
109
75
|
}
|
|
110
76
|
},
|
|
111
77
|
onError: (error) => {
|
|
112
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
113
|
-
console.log("[Purchase] onError called:", error);
|
|
114
|
-
}
|
|
115
78
|
|
|
116
79
|
// Use map-based lookup - O(1) complexity
|
|
117
80
|
const errorInfo = getErrorMessage(error);
|
|
118
81
|
|
|
119
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
120
|
-
console.log("[Purchase] Error info:", errorInfo);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
82
|
// Don't show alert for user cancellation
|
|
124
83
|
if (!errorInfo.shouldShowAlert) {
|
|
125
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
126
|
-
console.log("[Purchase] User cancelled - not showing alert");
|
|
127
|
-
}
|
|
128
84
|
return;
|
|
129
85
|
}
|
|
130
86
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* React hook for RevenueCat subscription management
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { useState, useCallback } from "react";
|
|
6
|
+
import { useState, useCallback, useEffect, useRef } from "react";
|
|
7
7
|
import type { PurchasesOffering, PurchasesPackage } from "react-native-purchases";
|
|
8
8
|
import { getRevenueCatService } from '../../infrastructure/services/RevenueCatService';
|
|
9
9
|
import type { PurchaseResult, RestoreResult } from '../../../../shared/application/ports/IRevenueCatService';
|
|
@@ -35,8 +35,18 @@ export interface UseRevenueCatResult {
|
|
|
35
35
|
export function useRevenueCat(): UseRevenueCatResult {
|
|
36
36
|
const [offering, setOffering] = useState<PurchasesOffering | null>(null);
|
|
37
37
|
const [loading, setLoading] = useState(false);
|
|
38
|
+
const isMountedRef = useRef(true);
|
|
39
|
+
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
isMountedRef.current = true;
|
|
42
|
+
return () => {
|
|
43
|
+
isMountedRef.current = false;
|
|
44
|
+
};
|
|
45
|
+
}, []);
|
|
38
46
|
|
|
39
47
|
const initialize = useCallback(async (userId: string, apiKey?: string) => {
|
|
48
|
+
if (!isMountedRef.current) return;
|
|
49
|
+
|
|
40
50
|
setLoading(true);
|
|
41
51
|
try {
|
|
42
52
|
const service = getRevenueCatService();
|
|
@@ -44,17 +54,21 @@ export function useRevenueCat(): UseRevenueCatResult {
|
|
|
44
54
|
return;
|
|
45
55
|
}
|
|
46
56
|
const result = await service.initialize(userId, apiKey);
|
|
47
|
-
if (result.success) {
|
|
57
|
+
if (result.success && isMountedRef.current) {
|
|
48
58
|
setOffering(result.offering);
|
|
49
59
|
}
|
|
50
60
|
} catch {
|
|
51
61
|
// Error handling is done by service
|
|
52
62
|
} finally {
|
|
53
|
-
|
|
63
|
+
if (isMountedRef.current) {
|
|
64
|
+
setLoading(false);
|
|
65
|
+
}
|
|
54
66
|
}
|
|
55
67
|
}, []);
|
|
56
68
|
|
|
57
69
|
const loadOfferings = useCallback(async () => {
|
|
70
|
+
if (!isMountedRef.current) return;
|
|
71
|
+
|
|
58
72
|
setLoading(true);
|
|
59
73
|
try {
|
|
60
74
|
const service = getRevenueCatService();
|
|
@@ -62,11 +76,15 @@ export function useRevenueCat(): UseRevenueCatResult {
|
|
|
62
76
|
return;
|
|
63
77
|
}
|
|
64
78
|
const fetchedOffering = await service.fetchOfferings();
|
|
65
|
-
|
|
79
|
+
if (isMountedRef.current) {
|
|
80
|
+
setOffering(fetchedOffering);
|
|
81
|
+
}
|
|
66
82
|
} catch {
|
|
67
83
|
// Error handling is done by service
|
|
68
84
|
} finally {
|
|
69
|
-
|
|
85
|
+
if (isMountedRef.current) {
|
|
86
|
+
setLoading(false);
|
|
87
|
+
}
|
|
70
88
|
}
|
|
71
89
|
}, []);
|
|
72
90
|
|
|
@@ -3,22 +3,12 @@ import type { RevenueCatConfig } from "../../../revenuecat/core/types";
|
|
|
3
3
|
import { ListenerState } from "./listeners/ListenerState";
|
|
4
4
|
import { processCustomerInfo } from "./listeners/CustomerInfoHandler";
|
|
5
5
|
|
|
6
|
-
declare const __DEV__: boolean;
|
|
7
|
-
|
|
8
6
|
export class CustomerInfoListenerManager {
|
|
9
7
|
private state = new ListenerState();
|
|
10
8
|
|
|
11
9
|
setUserId(userId: string, config: RevenueCatConfig): void {
|
|
12
10
|
const wasUserChange = this.state.hasUserChanged(userId);
|
|
13
11
|
|
|
14
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
15
|
-
console.log("[CustomerInfoListener] setUserId called:", {
|
|
16
|
-
userId,
|
|
17
|
-
wasUserChange,
|
|
18
|
-
hasListener: !!this.state.listener,
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
|
|
22
12
|
if (wasUserChange) {
|
|
23
13
|
this.removeListener();
|
|
24
14
|
this.state.resetRenewalState();
|
|
@@ -32,9 +22,6 @@ export class CustomerInfoListenerManager {
|
|
|
32
22
|
}
|
|
33
23
|
|
|
34
24
|
clearUserId(): void {
|
|
35
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
36
|
-
console.log("[CustomerInfoListener] clearUserId called");
|
|
37
|
-
}
|
|
38
25
|
this.state.currentUserId = null;
|
|
39
26
|
this.state.resetRenewalState();
|
|
40
27
|
}
|
|
@@ -42,10 +29,6 @@ export class CustomerInfoListenerManager {
|
|
|
42
29
|
setupListener(config: RevenueCatConfig): void {
|
|
43
30
|
this.removeListener();
|
|
44
31
|
|
|
45
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
46
|
-
console.log("[CustomerInfoListener] setupListener: Registering listener");
|
|
47
|
-
}
|
|
48
|
-
|
|
49
32
|
this.state.listener = async (customerInfo: CustomerInfo) => {
|
|
50
33
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
51
34
|
console.log("[CustomerInfoListener] 🔔 LISTENER TRIGGERED!", {
|
|
@@ -57,9 +40,6 @@ export class CustomerInfoListenerManager {
|
|
|
57
40
|
|
|
58
41
|
const capturedUserId = this.state.currentUserId;
|
|
59
42
|
if (!capturedUserId) {
|
|
60
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
61
|
-
console.log("[CustomerInfoListener] No userId - skipping");
|
|
62
|
-
}
|
|
63
43
|
return;
|
|
64
44
|
}
|
|
65
45
|
|
|
@@ -72,21 +52,11 @@ export class CustomerInfoListenerManager {
|
|
|
72
52
|
|
|
73
53
|
if (this.state.currentUserId === capturedUserId) {
|
|
74
54
|
this.state.renewalState = newRenewalState;
|
|
75
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
76
|
-
console.log("[CustomerInfoListener] processCustomerInfo completed");
|
|
77
|
-
}
|
|
78
|
-
} else {
|
|
79
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
80
|
-
console.log("[CustomerInfoListener] User changed during processing - discarding result");
|
|
81
|
-
}
|
|
82
55
|
}
|
|
56
|
+
// else: User switched during async operation, discard stale renewal state
|
|
83
57
|
};
|
|
84
58
|
|
|
85
59
|
Purchases.addCustomerInfoUpdateListener(this.state.listener);
|
|
86
|
-
|
|
87
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
88
|
-
console.log("[CustomerInfoListener] Listener registered successfully");
|
|
89
|
-
}
|
|
90
60
|
}
|
|
91
61
|
|
|
92
62
|
removeListener(): void {
|
|
@@ -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
|
|