@umituz/react-native-subscription 2.27.113 → 2.27.114

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.
Files changed (42) hide show
  1. package/package.json +1 -1
  2. package/src/domains/credits/application/CreditsInitializer.ts +27 -116
  3. package/src/domains/credits/application/credit-strategies/CreditAllocationOrchestrator.ts +1 -6
  4. package/src/domains/credits/application/creditDocumentHelpers.ts +58 -0
  5. package/src/domains/credits/application/creditOperationUtils.ts +154 -0
  6. package/src/domains/credits/presentation/useCredits.ts +1 -2
  7. package/src/domains/paywall/hooks/usePaywallActions.ts +0 -3
  8. package/src/domains/subscription/application/SubscriptionSyncService.ts +16 -20
  9. package/src/domains/subscription/core/RevenueCatError.ts +40 -31
  10. package/src/domains/subscription/infrastructure/handlers/PackageHandler.ts +0 -1
  11. package/src/domains/subscription/infrastructure/hooks/useRevenueCatTrialEligibility.ts +19 -85
  12. package/src/domains/subscription/infrastructure/managers/SubscriptionManager.ts +33 -75
  13. package/src/domains/subscription/infrastructure/managers/subscriptionManagerUtils.ts +57 -0
  14. package/src/domains/subscription/infrastructure/services/CustomerInfoListenerManager.ts +3 -12
  15. package/src/domains/subscription/infrastructure/services/PurchaseHandler.ts +0 -2
  16. package/src/domains/subscription/infrastructure/services/RevenueCatInitializer.ts +2 -4
  17. package/src/domains/subscription/infrastructure/services/RevenueCatService.ts +1 -5
  18. package/src/domains/subscription/infrastructure/utils/PremiumStatusSyncer.ts +3 -12
  19. package/src/domains/subscription/infrastructure/utils/authPurchaseState.ts +69 -0
  20. package/src/domains/subscription/infrastructure/utils/trialEligibilityUtils.ts +77 -0
  21. package/src/domains/subscription/presentation/components/feedback/FeedbackOption.tsx +139 -0
  22. package/src/domains/subscription/presentation/components/feedback/PaywallFeedbackModal.tsx +15 -70
  23. package/src/domains/subscription/presentation/components/feedback/paywallFeedbackStyles.ts +0 -92
  24. package/src/domains/subscription/presentation/stores/purchaseLoadingStore.ts +1 -18
  25. package/src/domains/subscription/presentation/useAuthAwarePurchase.ts +19 -69
  26. package/src/domains/subscription/presentation/usePremium.ts +2 -11
  27. package/src/domains/subscription/presentation/useSubscriptionStatus.ts +1 -6
  28. package/src/domains/trial/application/TrialService.ts +4 -8
  29. package/src/domains/wallet/infrastructure/repositories/TransactionRepository.ts +1 -1
  30. package/src/domains/wallet/infrastructure/services/ProductMetadataService.ts +0 -13
  31. package/src/domains/wallet/presentation/hooks/useProductMetadata.ts +0 -10
  32. package/src/domains/wallet/presentation/hooks/useTransactionHistory.ts +0 -8
  33. package/src/init/createSubscriptionInitModule.ts +1 -4
  34. package/src/presentation/hooks/feedback/useFeedbackSubmit.ts +0 -14
  35. package/src/shared/application/FeedbackService.ts +0 -21
  36. package/src/shared/infrastructure/SubscriptionEventBus.ts +1 -2
  37. package/src/shared/types/CommonTypes.ts +65 -0
  38. package/src/shared/utils/BaseError.ts +26 -0
  39. package/src/shared/utils/Logger.ts +14 -45
  40. package/src/shared/utils/Result.ts +16 -0
  41. package/src/shared/utils/SubscriptionError.ts +20 -30
  42. package/src/utils/packageTypeDetector.ts +0 -4
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Feedback Option Component
3
+ * Single feedback option with radio button and optional text input
4
+ */
5
+
6
+ import React from "react";
7
+ import { View, TouchableOpacity, TextInput, StyleSheet } from "react-native";
8
+ import { AtomicText, useAppDesignTokens } from "@umituz/react-native-design-system";
9
+
10
+ interface FeedbackOptionProps {
11
+ isSelected: boolean;
12
+ text: string;
13
+ showInput: boolean;
14
+ placeholder: string;
15
+ inputValue: string;
16
+ onSelect: () => void;
17
+ onChangeText: (text: string) => void;
18
+ }
19
+
20
+ export const FeedbackOption: React.FC<FeedbackOptionProps> = React.memo(({
21
+ isSelected,
22
+ text,
23
+ showInput,
24
+ placeholder,
25
+ inputValue,
26
+ onSelect,
27
+ onChangeText,
28
+ }) => {
29
+ const tokens = useAppDesignTokens();
30
+
31
+ const containerStyle = {
32
+ marginBottom: tokens.spacing.sm,
33
+ backgroundColor: tokens.colors.surfaceVariant,
34
+ borderRadius: tokens.borderRadius.md,
35
+ overflow: "hidden" as const,
36
+ };
37
+
38
+ return (
39
+ <View style={containerStyle}>
40
+ <TouchableOpacity
41
+ style={[
42
+ styles.optionRow,
43
+ {
44
+ borderBottomWidth: showInput ? 1 : 0,
45
+ borderBottomColor: tokens.colors.border,
46
+ paddingVertical: tokens.spacing.md,
47
+ paddingHorizontal: tokens.spacing.md,
48
+ },
49
+ ]}
50
+ onPress={onSelect}
51
+ activeOpacity={0.7}
52
+ >
53
+ <AtomicText
54
+ type="bodyMedium"
55
+ style={[
56
+ styles.optionText,
57
+ isSelected && { color: tokens.colors.primary, fontWeight: "600" },
58
+ ]}
59
+ >
60
+ {text}
61
+ </AtomicText>
62
+
63
+ <View
64
+ style={[
65
+ styles.radioButton,
66
+ {
67
+ borderColor: isSelected ? tokens.colors.primary : tokens.colors.border,
68
+ backgroundColor: isSelected ? tokens.colors.primary : "transparent",
69
+ },
70
+ ]}
71
+ >
72
+ {isSelected && (
73
+ <View style={[styles.radioButtonInner, { backgroundColor: tokens.colors.primary }]} />
74
+ )}
75
+ </View>
76
+ </TouchableOpacity>
77
+
78
+ {showInput && (
79
+ <View style={styles.inputContainer}>
80
+ <TextInput
81
+ style={[
82
+ styles.textInput,
83
+ {
84
+ backgroundColor: tokens.colors.surface,
85
+ borderRadius: tokens.borderRadius.sm,
86
+ padding: tokens.spacing.sm,
87
+ color: tokens.colors.textPrimary,
88
+ minHeight: 80,
89
+ textAlignVertical: "top",
90
+ },
91
+ ]}
92
+ placeholder={placeholder}
93
+ placeholderTextColor={tokens.colors.textTertiary}
94
+ multiline
95
+ maxLength={200}
96
+ value={inputValue}
97
+ onChangeText={onChangeText}
98
+ autoFocus
99
+ />
100
+ </View>
101
+ )}
102
+ </View>
103
+ );
104
+ });
105
+
106
+ FeedbackOption.displayName = "FeedbackOption";
107
+
108
+ const styles = StyleSheet.create({
109
+ optionRow: {
110
+ flexDirection: "row",
111
+ alignItems: "center",
112
+ justifyContent: "space-between",
113
+ },
114
+ optionText: {
115
+ flex: 1,
116
+ marginRight: 12,
117
+ },
118
+ radioButton: {
119
+ width: 22,
120
+ height: 22,
121
+ borderRadius: 11,
122
+ borderWidth: 2,
123
+ justifyContent: "center",
124
+ alignItems: "center",
125
+ },
126
+ radioButtonInner: {
127
+ width: 12,
128
+ height: 12,
129
+ borderRadius: 6,
130
+ },
131
+ inputContainer: {
132
+ padding: 12,
133
+ },
134
+ textInput: {
135
+ fontSize: 15,
136
+ borderWidth: 1,
137
+ borderColor: "#ccc",
138
+ },
139
+ });
@@ -1,8 +1,9 @@
1
1
  import React, { useMemo } from "react";
2
- import { View, TouchableOpacity, TextInput } from "react-native";
2
+ import { View, TouchableOpacity } from "react-native";
3
3
  import { AtomicText, BaseModal, useAppDesignTokens } from "@umituz/react-native-design-system";
4
4
  import { usePaywallFeedback } from "../../../../../presentation/hooks/feedback/usePaywallFeedback";
5
5
  import { createPaywallFeedbackStyles } from "./paywallFeedbackStyles";
6
+ import { FeedbackOption } from "./FeedbackOption";
6
7
 
7
8
  const FEEDBACK_OPTION_IDS = [
8
9
  "too_expensive",
@@ -69,77 +70,18 @@ export const PaywallFeedbackModal: React.FC<PaywallFeedbackModalProps> = React.m
69
70
  const isSelected = selectedReason === optionId;
70
71
  const isOther = optionId === "other";
71
72
  const showInput = isSelected && isOther;
72
- const displayText = translations.reasons[optionId];
73
-
74
- // Dynamic styles for the container
75
- const containerStyle = {
76
- marginBottom: tokens.spacing.sm,
77
- backgroundColor: tokens.colors.surfaceVariant,
78
- borderRadius: tokens.borderRadius.md,
79
- overflow: 'hidden' as const, // Ensure children don't bleed out
80
- };
81
73
 
82
74
  return (
83
- <View key={optionId} style={containerStyle}>
84
- <TouchableOpacity
85
- style={{
86
- borderBottomWidth: showInput ? 1 : 0,
87
- borderBottomColor: tokens.colors.surface, // Subtle separator
88
- paddingVertical: tokens.spacing.md,
89
- paddingHorizontal: tokens.spacing.md,
90
- flexDirection: 'row',
91
- alignItems: 'center',
92
- justifyContent: 'space-between',
93
- }}
94
- onPress={() => selectReason(optionId)}
95
- activeOpacity={0.7}
96
- >
97
- <AtomicText
98
- type="bodyMedium"
99
- style={[
100
- styles.optionText,
101
- isSelected && { color: tokens.colors.primary, fontWeight: '600' },
102
- ]}
103
- >
104
- {displayText}
105
- </AtomicText>
106
-
107
- <View
108
- style={[
109
- styles.radioButton,
110
- isSelected && styles.radioButtonSelected,
111
- ]}
112
- >
113
- {isSelected && (
114
- <View style={styles.radioButtonInner} />
115
- )}
116
- </View>
117
- </TouchableOpacity>
118
-
119
- {showInput && (
120
- <View style={[styles.inputContainer, { backgroundColor: 'transparent', marginTop: 0, padding: tokens.spacing.sm }]}>
121
- <TextInput
122
- style={[
123
- styles.textInput,
124
- {
125
- minHeight: 80,
126
- textAlignVertical: 'top',
127
- backgroundColor: tokens.colors.surface, // Slightly different background for input
128
- borderRadius: tokens.borderRadius.sm,
129
- padding: tokens.spacing.sm,
130
- }
131
- ]}
132
- placeholder={translations.otherPlaceholder}
133
- placeholderTextColor={tokens.colors.textTertiary}
134
- multiline
135
- maxLength={200}
136
- value={otherText}
137
- onChangeText={setOtherText}
138
- autoFocus
139
- />
140
- </View>
141
- )}
142
- </View>
75
+ <FeedbackOption
76
+ key={optionId}
77
+ isSelected={isSelected}
78
+ text={translations.reasons[optionId]}
79
+ showInput={showInput}
80
+ placeholder={translations.otherPlaceholder}
81
+ inputValue={otherText}
82
+ onSelect={() => selectReason(optionId)}
83
+ onChangeText={setOtherText}
84
+ />
143
85
  );
144
86
  })}
145
87
  </View>
@@ -159,4 +101,7 @@ export const PaywallFeedbackModal: React.FC<PaywallFeedbackModalProps> = React.m
159
101
  );
160
102
  });
161
103
 
104
+ PaywallFeedbackModal.displayName = "PaywallFeedbackModal";
105
+
106
+
162
107
 
@@ -10,105 +10,13 @@ export const createPaywallFeedbackStyles = (
10
10
  tokens: DesignTokens,
11
11
  canSubmit: boolean
12
12
  ) => {
13
- const overlayColor = tokens.colors.backgroundPrimary + '99';
14
-
15
13
  return StyleSheet.create({
16
- overlay: {
17
- flex: 1,
18
- backgroundColor: overlayColor,
19
- justifyContent: "center",
20
- alignItems: "center",
21
- padding: 20,
22
- },
23
- keyboardView: {
24
- width: "100%",
25
- alignItems: "center",
26
- },
27
- container: {
28
- width: "100%",
29
- maxWidth: 360,
30
- backgroundColor: tokens.colors.surface,
31
- borderRadius: 24,
32
- padding: 24,
33
- borderWidth: 1,
34
- borderColor: tokens.colors.border,
35
- },
36
- header: {
37
- alignItems: "center",
38
- marginBottom: 20,
39
- },
40
- title: {
41
- color: tokens.colors.textPrimary,
42
- marginBottom: 8,
43
- textAlign: "center",
44
- },
45
- subtitle: {
46
- color: tokens.colors.textSecondary,
47
- textAlign: "center",
48
- lineHeight: 22,
49
- },
50
14
  optionsContainer: {
51
15
  backgroundColor: tokens.colors.surfaceSecondary,
52
16
  borderRadius: 16,
53
17
  overflow: "hidden",
54
18
  marginBottom: 20,
55
19
  },
56
- optionRow: {
57
- flexDirection: "row",
58
- alignItems: "center",
59
- justifyContent: "space-between",
60
- paddingVertical: 16,
61
- paddingHorizontal: 16,
62
- borderBottomWidth: 1,
63
- borderBottomColor: tokens.colors.border,
64
- },
65
- optionRowLast: {
66
- borderBottomWidth: 0,
67
- },
68
- optionText: {
69
- color: tokens.colors.textSecondary,
70
- flex: 1,
71
- marginRight: 12,
72
- },
73
- optionTextSelected: {
74
- color: tokens.colors.textPrimary,
75
- fontWeight: "600",
76
- },
77
- radioButton: {
78
- width: 22,
79
- height: 22,
80
- borderRadius: 11,
81
- borderWidth: 2,
82
- borderColor: tokens.colors.border,
83
- justifyContent: "center",
84
- alignItems: "center",
85
- backgroundColor: "transparent",
86
- },
87
- radioButtonSelected: {
88
- borderColor: tokens.colors.primary,
89
- borderWidth: 2,
90
- },
91
- radioButtonInner: {
92
- width: 12,
93
- height: 12,
94
- borderRadius: 6,
95
- backgroundColor: tokens.colors.primary,
96
- },
97
- inputContainer: {
98
- paddingHorizontal: 16,
99
- paddingBottom: 16,
100
- },
101
- textInput: {
102
- backgroundColor: tokens.colors.surface,
103
- borderRadius: 12,
104
- borderWidth: 1,
105
- borderColor: tokens.colors.border,
106
- padding: 12,
107
- fontSize: 15,
108
- color: tokens.colors.textPrimary,
109
- minHeight: 80,
110
- textAlignVertical: "top",
111
- },
112
20
  submitButton: {
113
21
  backgroundColor: canSubmit ? tokens.colors.primary : tokens.colors.surfaceSecondary,
114
22
  borderRadius: 14,
@@ -32,21 +32,10 @@ const initialState: PurchaseLoadingState = {
32
32
  purchaseSource: null,
33
33
  };
34
34
 
35
- export const usePurchaseLoadingStore = create<PurchaseLoadingStore>((set, get) => ({
35
+ export const usePurchaseLoadingStore = create<PurchaseLoadingStore>((set) => ({
36
36
  ...initialState,
37
37
 
38
38
  startPurchase: (productId, source) => {
39
- const currentState = get();
40
- if (currentState.isPurchasing) {
41
- if (__DEV__) {
42
- console.warn("[PurchaseLoadingStore] startPurchase called while purchase already in progress:", {
43
- currentProductId: currentState.purchasingProductId,
44
- newProductId: productId,
45
- currentSource: currentState.purchaseSource,
46
- newSource: source,
47
- });
48
- }
49
- }
50
39
  set({
51
40
  isPurchasing: true,
52
41
  purchasingProductId: productId,
@@ -55,12 +44,6 @@ export const usePurchaseLoadingStore = create<PurchaseLoadingStore>((set, get) =
55
44
  },
56
45
 
57
46
  endPurchase: () => {
58
- const currentState = get();
59
- if (!currentState.isPurchasing) {
60
- if (__DEV__) {
61
- console.warn("[PurchaseLoadingStore] endPurchase called while no purchase in progress");
62
- }
63
- }
64
47
  set({
65
48
  isPurchasing: false,
66
49
  purchasingProductId: null,
@@ -7,59 +7,24 @@ import { useCallback } from "react";
7
7
  import type { PurchasesPackage } from "react-native-purchases";
8
8
  import { usePremium } from "./usePremium";
9
9
  import type { PurchaseSource } from "../core/SubscriptionConstants";
10
+ import { authPurchaseStateManager } from "../infrastructure/utils/authPurchaseState";
10
11
 
11
- export interface PurchaseAuthProvider {
12
- isAuthenticated: () => boolean;
13
- showAuthModal: () => void;
14
- }
15
-
16
- let globalAuthProvider: PurchaseAuthProvider | null = null;
17
-
18
- interface SavedPurchaseState {
19
- pkg: PurchasesPackage;
20
- source: PurchaseSource;
21
- timestamp: number;
22
- }
12
+ export type { PurchaseAuthProvider } from "../infrastructure/utils/authPurchaseState";
23
13
 
24
- const SAVED_PURCHASE_EXPIRY_MS = 5 * 60 * 1000; // 5 minutes
25
- let savedPurchaseState: SavedPurchaseState | null = null;
26
-
27
- export const configureAuthProvider = (provider: PurchaseAuthProvider): void => {
28
- globalAuthProvider = provider;
14
+ export const configureAuthProvider = (provider: import("../infrastructure/utils/authPurchaseState").PurchaseAuthProvider): void => {
15
+ authPurchaseStateManager.configure(provider);
29
16
  };
30
17
 
31
- /**
32
- * Cleanup method to reset global auth provider state
33
- * Call this when app is shutting down or auth system is being reset
34
- */
35
18
  export const cleanupAuthProvider = (): void => {
36
- if (__DEV__) {
37
- console.log("[useAuthAwarePurchase] Cleaning up auth provider");
38
- }
39
- globalAuthProvider = null;
40
- clearSavedPurchase();
41
- };
42
-
43
- const savePurchase = (pkg: PurchasesPackage, source: PurchaseSource): void => {
44
- savedPurchaseState = { pkg, source, timestamp: Date.now() };
19
+ authPurchaseStateManager.cleanup();
45
20
  };
46
21
 
47
22
  export const getSavedPurchase = (): { pkg: PurchasesPackage; source: PurchaseSource } | null => {
48
- if (!savedPurchaseState) {
49
- return null;
50
- }
51
-
52
- const isExpired = Date.now() - savedPurchaseState.timestamp > SAVED_PURCHASE_EXPIRY_MS;
53
- if (isExpired) {
54
- savedPurchaseState = null;
55
- return null;
56
- }
57
-
58
- return { pkg: savedPurchaseState.pkg, source: savedPurchaseState.source };
23
+ return authPurchaseStateManager.getSavedPurchase();
59
24
  };
60
25
 
61
26
  export const clearSavedPurchase = (): void => {
62
- savedPurchaseState = null;
27
+ authPurchaseStateManager.clearSavedPurchase();
63
28
  };
64
29
 
65
30
  export interface UseAuthAwarePurchaseParams {
@@ -79,25 +44,17 @@ export const useAuthAwarePurchase = (
79
44
 
80
45
  const handlePurchase = useCallback(
81
46
  async (pkg: PurchasesPackage, source?: PurchaseSource): Promise<boolean> => {
82
- if (typeof __DEV__ !== "undefined" && __DEV__) {
83
- console.log("[useAuthAwarePurchase] handlePurchase:", {
84
- productId: pkg.product.identifier,
85
- hasAuthProvider: !!globalAuthProvider,
86
- });
87
- }
47
+ const authProvider = authPurchaseStateManager.getProvider();
88
48
 
89
- if (!globalAuthProvider) {
90
- if (typeof __DEV__ !== "undefined" && __DEV__) {
91
- console.error("[useAuthAwarePurchase] Auth provider not configured");
92
- }
49
+ if (!authProvider) {
93
50
  return false;
94
51
  }
95
52
 
96
- const isAuth = globalAuthProvider.isAuthenticated();
53
+ const isAuth = authProvider.isAuthenticated();
97
54
 
98
55
  if (!isAuth) {
99
- savePurchase(pkg, source || params?.source || "settings");
100
- globalAuthProvider.showAuthModal();
56
+ authPurchaseStateManager.savePurchase(pkg, source || params?.source || "settings");
57
+ authProvider.showAuthModal();
101
58
  return false;
102
59
  }
103
60
 
@@ -107,12 +64,14 @@ export const useAuthAwarePurchase = (
107
64
  );
108
65
 
109
66
  const handleRestore = useCallback(async (): Promise<boolean> => {
110
- if (!globalAuthProvider) {
67
+ const authProvider = authPurchaseStateManager.getProvider();
68
+
69
+ if (!authProvider) {
111
70
  return false;
112
71
  }
113
72
 
114
- if (!globalAuthProvider.isAuthenticated()) {
115
- globalAuthProvider.showAuthModal();
73
+ if (!authProvider.isAuthenticated()) {
74
+ authProvider.showAuthModal();
116
75
  return false;
117
76
  }
118
77
 
@@ -120,27 +79,18 @@ export const useAuthAwarePurchase = (
120
79
  }, [restorePurchase]);
121
80
 
122
81
  const executeSavedPurchase = useCallback(async (): Promise<boolean> => {
123
- const saved = getSavedPurchase();
82
+ const saved = authPurchaseStateManager.getSavedPurchase();
124
83
  if (!saved) {
125
84
  return false;
126
85
  }
127
86
 
128
- if (typeof __DEV__ !== "undefined" && __DEV__) {
129
- console.log("[useAuthAwarePurchase] Executing saved purchase:", saved.pkg.product.identifier);
130
- }
131
-
132
87
  try {
133
88
  const result = await purchasePackage(saved.pkg);
134
- // Only clear after successful purchase
135
89
  if (result) {
136
- clearSavedPurchase();
90
+ authPurchaseStateManager.clearSavedPurchase();
137
91
  }
138
92
  return result;
139
93
  } catch (error) {
140
- // Don't clear on error - allow retry
141
- if (typeof __DEV__ !== "undefined" && __DEV__) {
142
- console.error("[useAuthAwarePurchase] Saved purchase failed:", error);
143
- }
144
94
  throw error;
145
95
  }
146
96
  }, [purchasePackage]);
@@ -47,16 +47,10 @@ export const usePremium = (): UsePremiumResult => {
47
47
 
48
48
  const handlePurchase = useCallback(
49
49
  async (pkg: PurchasesPackage): Promise<boolean> => {
50
- if (typeof __DEV__ !== "undefined" && __DEV__) {
51
- console.log("[usePremium] handlePurchase:", pkg.product.identifier);
52
- }
53
50
  try {
54
51
  const result = await purchaseMutation.mutateAsync(pkg);
55
52
  return result.success;
56
- } catch (error) {
57
- if (typeof __DEV__ !== "undefined" && __DEV__) {
58
- console.error("[usePremium] Purchase failed:", error);
59
- }
53
+ } catch {
60
54
  return false;
61
55
  }
62
56
  },
@@ -67,10 +61,7 @@ export const usePremium = (): UsePremiumResult => {
67
61
  try {
68
62
  const result = await restoreMutation.mutateAsync();
69
63
  return result.success;
70
- } catch (error) {
71
- if (typeof __DEV__ !== "undefined" && __DEV__) {
72
- console.error("[usePremium] Restore failed:", error);
73
- }
64
+ } catch {
74
65
  return false;
75
66
  }
76
67
  }, [restoreMutation]);
@@ -37,13 +37,8 @@ export const useSubscriptionStatus = (): SubscriptionStatusResult => {
37
37
 
38
38
  try {
39
39
  const result = await SubscriptionManager.checkPremiumStatus();
40
- // Ensure we always return a valid object even if result is null/undefined
41
40
  return result ?? { isPremium: false, expirationDate: null };
42
- } catch (error) {
43
- if (__DEV__) {
44
- console.error('[useSubscriptionStatus] Failed to check premium status:', error);
45
- }
46
- // Return default state on error to prevent crashes
41
+ } catch {
47
42
  return { isPremium: false, expirationDate: null };
48
43
  }
49
44
  },
@@ -18,8 +18,7 @@ export async function checkTrialEligibility(userId?: string, deviceId?: string):
18
18
  const id = deviceId || await getDeviceId();
19
19
  const record = await repository.getRecord(id);
20
20
  return TrialEligibilityService.check(userId, id, record);
21
- } catch (error) {
22
- if (__DEV__) console.error("[TrialService] Eligibility check error:", error);
21
+ } catch {
23
22
  return { eligible: false, reason: "error" };
24
23
  }
25
24
  }
@@ -34,8 +33,7 @@ export async function recordTrialStart(userId: string, deviceId?: string): Promi
34
33
  lastUserId: userId,
35
34
  userIds: arrayUnion(userId) as any,
36
35
  });
37
- } catch (error) {
38
- if (__DEV__) console.error("[TrialService] Record trial error:", error);
36
+ } catch {
39
37
  return false;
40
38
  }
41
39
  }
@@ -48,8 +46,7 @@ export async function recordTrialEnd(deviceId?: string): Promise<boolean> {
48
46
  trialInProgress: false,
49
47
  trialEndedAt: serverTimestamp() as any,
50
48
  });
51
- } catch (error) {
52
- if (__DEV__) console.error("[TrialService] Record trial end error:", error);
49
+ } catch {
53
50
  return false;
54
51
  }
55
52
  }
@@ -62,8 +59,7 @@ export async function recordTrialConversion(deviceId?: string): Promise<boolean>
62
59
  trialInProgress: false,
63
60
  trialConvertedAt: serverTimestamp() as any,
64
61
  });
65
- } catch (error) {
66
- if (__DEV__) console.error("[TrialService] Record conversion error:", error);
62
+ } catch {
67
63
  return false;
68
64
  }
69
65
  }
@@ -118,7 +118,7 @@ export class TransactionRepository extends BaseRepository {
118
118
  createdAt: Date.now(),
119
119
  },
120
120
  };
121
- } catch (error) {
121
+ } catch {
122
122
  return {
123
123
  success: false,
124
124
  error: {
@@ -58,21 +58,8 @@ export class ProductMetadataService {
58
58
  try {
59
59
  const data = await this.fetchFromFirebase();
60
60
  this.cache = { data, timestamp: Date.now() };
61
-
62
- if (__DEV__) {
63
- console.log(
64
- "[ProductMetadataService] Loaded:",
65
- data.length,
66
- "products"
67
- );
68
- }
69
-
70
61
  return data;
71
62
  } catch (error) {
72
- if (__DEV__) {
73
- console.error("[ProductMetadataService] Fetch error:", error);
74
- }
75
-
76
63
  if (this.cache) {
77
64
  return this.cache.data;
78
65
  }
@@ -72,16 +72,6 @@ export function useProductMetadata({
72
72
  const creditsPackages = products.filter((p) => p.type === "credits");
73
73
  const subscriptionPackages = products.filter((p) => p.type === "subscription");
74
74
 
75
- if (__DEV__) {
76
- console.log("[useProductMetadata] State", {
77
- enabled,
78
- isLoading,
79
- count: products.length,
80
- credits: creditsPackages.length,
81
- subscriptions: subscriptionPackages.length,
82
- });
83
- }
84
-
85
75
  return {
86
76
  products,
87
77
  isLoading,
@@ -69,14 +69,6 @@ export function useTransactionHistory({
69
69
 
70
70
  const transactions = data ?? [];
71
71
 
72
- if (typeof __DEV__ !== "undefined" && __DEV__) {
73
- console.log("[useTransactionHistory] State", {
74
- userId: userId?.slice(0, 8),
75
- isLoading,
76
- count: transactions.length,
77
- });
78
- }
79
-
80
72
  return {
81
73
  transactions,
82
74
  isLoading,