@umituz/react-native-subscription 2.26.19 → 2.27.0

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-subscription",
3
- "version": "2.26.19",
3
+ "version": "2.27.0",
4
4
  "description": "Complete subscription management with RevenueCat, paywall UI, and credits system for React Native apps",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -33,6 +33,7 @@
33
33
  },
34
34
  "peerDependencies": {
35
35
  "@tanstack/react-query": ">=5.0.0",
36
+ "@umituz/react-native-auth": ">=3.0.0",
36
37
  "expo-constants": ">=17.0.0",
37
38
  "expo-image": ">=3.0.0",
38
39
  "firebase": ">=10.0.0",
@@ -16,7 +16,6 @@ import type { PaywallContainerProps } from "./PaywallContainer.types";
16
16
 
17
17
  export const PaywallContainer: React.FC<PaywallContainerProps> = (props) => {
18
18
  const {
19
- userId,
20
19
  translations,
21
20
  mode = "subscription",
22
21
  legalUrls,
@@ -41,10 +40,9 @@ export const PaywallContainer: React.FC<PaywallContainerProps> = (props) => {
41
40
 
42
41
  const purchaseSource = source ?? currentSource ?? "settings";
43
42
 
44
- const { data: allPackages = [], isLoading } = useSubscriptionPackages(userId ?? undefined);
43
+ const { data: allPackages = [], isLoading } = useSubscriptionPackages();
45
44
  const { eligibilityMap, checkEligibility } = useRevenueCatTrialEligibility();
46
45
  const { handlePurchase, handleRestore } = usePaywallActions({
47
- userId: userId ?? undefined,
48
46
  source: purchaseSource,
49
47
  onPurchaseSuccess,
50
48
  onPurchaseError,
@@ -24,10 +24,6 @@ export interface TrialConfig {
24
24
  }
25
25
 
26
26
  export interface PaywallContainerProps {
27
- /** User ID for subscription management */
28
- readonly userId: string | null;
29
- /** Whether user is anonymous (requires auth before purchase) */
30
- readonly isAnonymous?: boolean;
31
27
  /** Paywall translations - no defaults, must be provided */
32
28
  readonly translations: PaywallTranslations;
33
29
  /** Paywall mode - subscription, credits, or hybrid */
@@ -7,7 +7,6 @@ import type { PurchaseSource } from "../../../domain/entities/Credits";
7
7
  declare const __DEV__: boolean;
8
8
 
9
9
  interface UsePaywallActionsProps {
10
- userId?: string;
11
10
  source?: PurchaseSource;
12
11
  onPurchaseSuccess?: () => void;
13
12
  onPurchaseError?: (error: string) => void;
@@ -16,38 +15,33 @@ interface UsePaywallActionsProps {
16
15
  }
17
16
 
18
17
  export const usePaywallActions = ({
19
- userId,
20
18
  source,
21
19
  onPurchaseSuccess,
22
20
  onPurchaseError,
23
21
  onAuthRequired: _onAuthRequired,
24
22
  onClose,
25
23
  }: UsePaywallActionsProps) => {
26
- const { handlePurchase: authAwarePurchase } = useAuthAwarePurchase({ source, userId });
27
- const { mutateAsync: restorePurchases } = useRestorePurchase(userId);
24
+ const { handlePurchase: authAwarePurchase } = useAuthAwarePurchase({ source });
25
+ const { mutateAsync: restorePurchases } = useRestorePurchase();
28
26
 
29
27
  const handlePurchase = useCallback(async (pkg: PurchasesPackage) => {
30
28
  try {
31
- if (__DEV__) console.log("[PaywallActions] Purchase started:", pkg.product.identifier);
29
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
30
+ console.log("[PaywallActions] Purchase started:", pkg.product.identifier);
31
+ }
32
32
  const res = await authAwarePurchase(pkg, source);
33
- if (__DEV__) console.log("[PaywallActions] Purchase result:", { res, productId: pkg.product.identifier });
34
33
  if (res) {
35
- if (__DEV__) console.log("[PaywallActions] Purchase successful, closing paywall");
36
34
  onPurchaseSuccess?.();
37
35
  onClose();
38
- } else {
39
- if (__DEV__) console.log("[PaywallActions] Purchase returned false, paywall stays open");
40
36
  }
41
37
  } catch (err: unknown) {
42
38
  const message = err instanceof Error ? err.message : String(err);
43
- if (__DEV__) console.log("[PaywallActions] Purchase error:", message);
44
39
  onPurchaseError?.(message);
45
40
  }
46
41
  }, [authAwarePurchase, source, onClose, onPurchaseSuccess, onPurchaseError]);
47
42
 
48
43
  const handleRestore = useCallback(async () => {
49
44
  try {
50
- if (__DEV__) console.log("[PaywallActions] Restore started");
51
45
  const res = await restorePurchases();
52
46
  if (res.success) {
53
47
  onPurchaseSuccess?.();
@@ -2,11 +2,15 @@
2
2
  * useTransactionHistory Hook
3
3
  *
4
4
  * TanStack Query hook for fetching credit transaction history.
5
- * Generic and reusable - uses config from TransactionRepository.
5
+ * Auth info automatically read from @umituz/react-native-auth.
6
6
  */
7
7
 
8
8
  import { useQuery } from "@umituz/react-native-design-system";
9
9
  import { useMemo } from "react";
10
+ import {
11
+ useAuthStore,
12
+ selectUserId,
13
+ } from "@umituz/react-native-auth";
10
14
  import type {
11
15
  CreditLog,
12
16
  TransactionRepositoryConfig,
@@ -15,21 +19,14 @@ import { TransactionRepository } from "../../infrastructure/repositories/Transac
15
19
 
16
20
  declare const __DEV__: boolean;
17
21
 
18
- const CACHE_CONFIG = {
19
- staleTime: 60 * 1000, // 1 minute
20
- gcTime: 5 * 60 * 1000, // 5 minutes
21
- };
22
-
23
22
  export const transactionQueryKeys = {
24
23
  all: ["transactions"] as const,
25
24
  user: (userId: string) => ["transactions", userId] as const,
26
25
  };
27
26
 
28
27
  export interface UseTransactionHistoryParams {
29
- userId: string | undefined;
30
28
  config: TransactionRepositoryConfig;
31
29
  limit?: number;
32
- enabled?: boolean;
33
30
  }
34
31
 
35
32
  export interface UseTransactionHistoryResult {
@@ -41,12 +38,11 @@ export interface UseTransactionHistoryResult {
41
38
  }
42
39
 
43
40
  export function useTransactionHistory({
44
- userId,
45
41
  config,
46
42
  limit = 50,
47
- enabled = true,
48
43
  }: UseTransactionHistoryParams): UseTransactionHistoryResult {
49
- // Memoize repository to prevent recreation on every render
44
+ const userId = useAuthStore(selectUserId);
45
+
50
46
  const repository = useMemo(
51
47
  () => new TransactionRepository(config),
52
48
  [config]
@@ -68,17 +64,16 @@ export function useTransactionHistory({
68
64
 
69
65
  return result.data ?? [];
70
66
  },
71
- enabled: enabled && !!userId,
72
- staleTime: CACHE_CONFIG.staleTime,
73
- gcTime: CACHE_CONFIG.gcTime,
67
+ enabled: !!userId,
68
+ staleTime: 0,
69
+ gcTime: 0,
74
70
  });
75
71
 
76
72
  const transactions = data ?? [];
77
73
 
78
- if (__DEV__) {
74
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
79
75
  console.log("[useTransactionHistory] State", {
80
- userId,
81
- enabled,
76
+ userId: userId?.slice(0, 8),
82
77
  isLoading,
83
78
  count: transactions.length,
84
79
  });
@@ -6,21 +6,16 @@
6
6
  */
7
7
 
8
8
  import { useCallback, useMemo } from "react";
9
+ import { useCredits } from "../../../../presentation/hooks/useCredits";
9
10
  import {
10
- useCredits,
11
- type UseCreditsParams,
12
- } from "../../../../presentation/hooks/useCredits";
13
- import {
14
- useTransactionHistory,
15
- type UseTransactionHistoryParams,
11
+ useTransactionHistory,
12
+ type UseTransactionHistoryParams,
16
13
  } from "./useTransactionHistory";
17
14
  import type { CreditLog } from "../../domain/types/transaction.types";
18
15
 
19
16
  export interface UseWalletParams {
20
- userId: string | undefined;
21
17
  transactionConfig: UseTransactionHistoryParams["config"];
22
18
  transactionLimit?: number;
23
- enabled?: boolean;
24
19
  }
25
20
 
26
21
  export interface UseWalletResult {
@@ -35,35 +30,24 @@ export interface UseWalletResult {
35
30
  }
36
31
 
37
32
  export function useWallet({
38
- userId,
39
33
  transactionConfig,
40
34
  transactionLimit = 50,
41
- enabled = true,
42
35
  }: UseWalletParams): UseWalletResult {
43
- const creditsParams: UseCreditsParams = {
44
- userId,
45
- enabled,
46
- };
47
-
48
- const transactionParams: UseTransactionHistoryParams = {
49
- userId,
50
- config: transactionConfig,
51
- limit: transactionLimit,
52
- enabled,
53
- };
54
-
55
36
  const {
56
37
  credits,
57
38
  isLoading: balanceLoading,
58
39
  refetch: refetchBalance,
59
40
  hasCredits,
60
- } = useCredits(creditsParams);
41
+ } = useCredits();
61
42
 
62
43
  const {
63
44
  transactions,
64
45
  isLoading: transactionsLoading,
65
46
  refetch: refetchTransactions,
66
- } = useTransactionHistory(transactionParams);
47
+ } = useTransactionHistory({
48
+ config: transactionConfig,
49
+ limit: transactionLimit,
50
+ });
67
51
 
68
52
  const balance = useMemo(() => {
69
53
  return credits?.credits ?? 0;
@@ -37,7 +37,6 @@ export const clearSavedPurchase = (): void => {
37
37
 
38
38
  export interface UseAuthAwarePurchaseParams {
39
39
  source?: PurchaseSource;
40
- userId?: string;
41
40
  }
42
41
 
43
42
  export interface UseAuthAwarePurchaseResult {
@@ -49,63 +48,44 @@ export interface UseAuthAwarePurchaseResult {
49
48
  export const useAuthAwarePurchase = (
50
49
  params?: UseAuthAwarePurchaseParams
51
50
  ): UseAuthAwarePurchaseResult => {
52
- const { purchasePackage, restorePurchase } = usePremium(params?.userId);
51
+ const { purchasePackage, restorePurchase } = usePremium();
53
52
 
54
53
  const handlePurchase = useCallback(
55
54
  async (pkg: PurchasesPackage, source?: PurchaseSource): Promise<boolean> => {
56
- if (__DEV__) {
57
- console.log("[useAuthAwarePurchase] handlePurchase called:", {
55
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
56
+ console.log("[useAuthAwarePurchase] handlePurchase:", {
58
57
  productId: pkg.product.identifier,
59
58
  hasAuthProvider: !!globalAuthProvider,
60
59
  });
61
60
  }
62
61
 
63
62
  if (!globalAuthProvider) {
64
- if (__DEV__) {
63
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
65
64
  console.error("[useAuthAwarePurchase] Auth provider not configured");
66
65
  }
67
66
  return false;
68
67
  }
69
68
 
70
69
  const isAuth = globalAuthProvider.isAuthenticated();
71
- if (__DEV__) {
72
- console.log("[useAuthAwarePurchase] Auth check:", { isAuthenticated: isAuth });
73
- }
74
70
 
75
71
  if (!isAuth) {
76
- if (__DEV__) {
77
- console.log("[useAuthAwarePurchase] Not authenticated, saving and showing auth");
78
- }
79
72
  savedPackage = pkg;
80
73
  savedSource = source || params?.source || "settings";
81
74
  globalAuthProvider.showAuthModal();
82
75
  return false;
83
76
  }
84
77
 
85
- if (__DEV__) {
86
- console.log("[useAuthAwarePurchase] Calling purchasePackage");
87
- }
88
- const result = await purchasePackage(pkg);
89
- if (__DEV__) {
90
- console.log("[useAuthAwarePurchase] purchasePackage returned:", result);
91
- }
92
- return result;
78
+ return purchasePackage(pkg);
93
79
  },
94
80
  [purchasePackage, params?.source]
95
81
  );
96
82
 
97
83
  const handleRestore = useCallback(async (): Promise<boolean> => {
98
84
  if (!globalAuthProvider) {
99
- if (__DEV__) {
100
- console.error("[useAuthAwarePurchase] Auth provider not configured");
101
- }
102
85
  return false;
103
86
  }
104
87
 
105
88
  if (!globalAuthProvider.isAuthenticated()) {
106
- if (__DEV__) {
107
- console.log("[useAuthAwarePurchase] Not authenticated for restore");
108
- }
109
89
  globalAuthProvider.showAuthModal();
110
90
  return false;
111
91
  }
@@ -116,13 +96,10 @@ export const useAuthAwarePurchase = (
116
96
  const executeSavedPurchase = useCallback(async (): Promise<boolean> => {
117
97
  const saved = getSavedPurchase();
118
98
  if (!saved) {
119
- if (__DEV__) {
120
- console.log("[useAuthAwarePurchase] No saved purchase to execute");
121
- }
122
99
  return false;
123
100
  }
124
101
 
125
- if (__DEV__) {
102
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
126
103
  console.log("[useAuthAwarePurchase] Executing saved purchase:", saved.pkg.product.identifier);
127
104
  }
128
105
 
@@ -1,18 +1,23 @@
1
1
  /**
2
2
  * useCredits Hook
3
3
  *
4
- * TanStack Query hook for fetching user credits.
5
- * Generic and reusable - uses config from module-level provider.
6
- * Auto-initializes free credits for new users if configured.
4
+ * Fetches user credits - NO CACHE, always fresh data.
5
+ * Auth info automatically read from @umituz/react-native-auth.
6
+ * Auto-initializes free credits for registered users only.
7
7
  */
8
8
 
9
9
  import { useQuery } from "@umituz/react-native-design-system";
10
10
  import { useCallback, useMemo, useEffect } from "react";
11
+ import {
12
+ useAuthStore,
13
+ selectUserId,
14
+ selectIsAnonymous,
15
+ } from "@umituz/react-native-auth";
11
16
  import type { UserCredits } from "../../domain/entities/Credits";
12
17
  import {
13
- getCreditsRepository,
14
- getCreditsConfig,
15
- isCreditsRepositoryConfigured,
18
+ getCreditsRepository,
19
+ getCreditsConfig,
20
+ isCreditsRepositoryConfigured,
16
21
  } from "../../infrastructure/repositories/CreditsRepositoryProvider";
17
22
 
18
23
  declare const __DEV__: boolean;
@@ -22,33 +27,8 @@ export const creditsQueryKeys = {
22
27
  user: (userId: string) => ["credits", userId] as const,
23
28
  };
24
29
 
25
- /** Default stale time: 30 seconds - prevents infinite re-render loops */
26
- const DEFAULT_STALE_TIME = 30 * 1000;
27
- /** Default gc time: 5 minutes */
28
- const DEFAULT_GC_TIME = 5 * 60 * 1000;
29
-
30
- /**
31
- * Global tracker for free credits initialization attempts.
32
- * Shared across all useCredits hook instances to prevent multiple inits.
33
- */
34
30
  const freeCreditsInitAttempted = new Set<string>();
35
31
 
36
- export interface CreditsCacheConfig {
37
- /** Time in ms before data is considered stale. Default: 30 seconds */
38
- staleTime?: number;
39
- /** Time in ms before inactive data is garbage collected. Default: 5 minutes */
40
- gcTime?: number;
41
- }
42
-
43
- export interface UseCreditsParams {
44
- userId: string | undefined;
45
- enabled?: boolean;
46
- /** Whether user is anonymous. Anonymous users don't get free credits. */
47
- isAnonymous?: boolean;
48
- /** Cache configuration. Default: 30 second staleTime, 5 minute gcTime */
49
- cache?: CreditsCacheConfig;
50
- }
51
-
52
32
  export interface UseCreditsResult {
53
33
  credits: UserCredits | null;
54
34
  isLoading: boolean;
@@ -56,24 +36,18 @@ export interface UseCreditsResult {
56
36
  hasCredits: boolean;
57
37
  creditsPercent: number;
58
38
  refetch: () => void;
59
- /** Check if user can afford a specific credit cost */
60
39
  canAfford: (cost: number) => boolean;
61
40
  }
62
41
 
63
- export const useCredits = ({
64
- userId,
65
- enabled = true,
66
- isAnonymous = false,
67
- cache,
68
- }: UseCreditsParams): UseCreditsResult => {
42
+ export const useCredits = (): UseCreditsResult => {
43
+ const userId = useAuthStore(selectUserId);
44
+ const isAnonymous = useAuthStore(selectIsAnonymous);
45
+ const isRegisteredUser = !!userId && !isAnonymous;
46
+
69
47
  const isConfigured = isCreditsRepositoryConfigured();
70
48
  const config = getCreditsConfig();
71
49
 
72
- // Default: 30 second stale time to prevent infinite re-render loops
73
- const staleTime = cache?.staleTime ?? DEFAULT_STALE_TIME;
74
- const gcTime = cache?.gcTime ?? DEFAULT_GC_TIME;
75
-
76
- const queryEnabled = enabled && !!userId && isConfigured;
50
+ const queryEnabled = !!userId && isConfigured;
77
51
 
78
52
  const { data, isLoading, error, refetch, isFetched } = useQuery({
79
53
  queryKey: creditsQueryKeys.user(userId ?? ""),
@@ -87,7 +61,6 @@ export const useCredits = ({
87
61
  throw new Error(result.error?.message || "Failed to fetch credits");
88
62
  }
89
63
 
90
- // Background sync: If mapper detected expired status, sync to Firestore
91
64
  if (result.data?.status === "expired") {
92
65
  repository.syncExpiredStatus(userId).catch((syncError) => {
93
66
  if (typeof __DEV__ !== "undefined" && __DEV__) {
@@ -99,8 +72,8 @@ export const useCredits = ({
99
72
  return result.data || null;
100
73
  },
101
74
  enabled: queryEnabled,
102
- staleTime,
103
- gcTime,
75
+ staleTime: 0,
76
+ gcTime: 0,
104
77
  refetchOnMount: true,
105
78
  refetchOnWindowFocus: true,
106
79
  refetchOnReconnect: true,
@@ -108,33 +81,23 @@ export const useCredits = ({
108
81
 
109
82
  const credits = data ?? null;
110
83
 
111
- // Auto-initialize free credits for new users
112
84
  const freeCredits = config.freeCredits ?? 0;
113
85
  const autoInit = config.autoInitializeFreeCredits !== false && freeCredits > 0;
114
86
 
115
87
  useEffect(() => {
116
- // Only run if:
117
- // 1. Query has completed (isFetched)
118
- // 2. User is authenticated (not anonymous)
119
- // 3. No credits data exists
120
- // 4. Free credits configured
121
- // 5. Auto-init enabled
122
- // 6. Haven't already attempted for this user (global tracking)
123
- // 7. User is NOT anonymous (anonymous users must register first)
124
88
  if (
125
89
  isFetched &&
126
90
  userId &&
127
- !isAnonymous &&
91
+ isRegisteredUser &&
128
92
  isConfigured &&
129
93
  !credits &&
130
94
  autoInit &&
131
95
  !freeCreditsInitAttempted.has(userId)
132
96
  ) {
133
- // Mark as attempted IMMEDIATELY to prevent other hook instances
134
97
  freeCreditsInitAttempted.add(userId);
135
98
 
136
99
  if (typeof __DEV__ !== "undefined" && __DEV__) {
137
- console.log("[useCredits] Auto-initializing free credits for new registered user:", userId.slice(0, 8));
100
+ console.log("[useCredits] Initializing free credits for registered user:", userId.slice(0, 8));
138
101
  }
139
102
 
140
103
  const repository = getCreditsRepository();
@@ -152,25 +115,20 @@ export const useCredits = ({
152
115
  });
153
116
  } else if (isFetched && userId && isAnonymous && !credits && autoInit) {
154
117
  if (typeof __DEV__ !== "undefined" && __DEV__) {
155
- console.log("[useCredits] Skipping free credits for anonymous user - registration required");
118
+ console.log("[useCredits] Skipping free credits - anonymous user must register first");
156
119
  }
157
120
  }
158
- }, [isFetched, userId, isAnonymous, isConfigured, credits, autoInit, refetch]);
121
+ }, [isFetched, userId, isRegisteredUser, isAnonymous, isConfigured, credits, autoInit, refetch]);
159
122
 
160
- // Memoize derived values to prevent unnecessary re-renders
161
123
  const derivedValues = useMemo(() => {
162
124
  const has = (credits?.credits ?? 0) > 0;
163
125
  const percent = credits
164
126
  ? Math.round((credits.credits / config.creditLimit) * 100)
165
127
  : 0;
166
128
 
167
- return {
168
- hasCredits: has,
169
- creditsPercent: percent,
170
- };
129
+ return { hasCredits: has, creditsPercent: percent };
171
130
  }, [credits, config.creditLimit]);
172
131
 
173
- // Memoize canAfford to prevent recreation on every render
174
132
  const canAfford = useCallback(
175
133
  (cost: number): boolean => {
176
134
  if (!credits) return false;
@@ -190,11 +148,7 @@ export const useCredits = ({
190
148
  };
191
149
  };
192
150
 
193
- export const useHasCredits = (
194
- userId: string | undefined,
195
- isAnonymous?: boolean
196
- ): boolean => {
197
- const { credits } = useCredits({ userId, isAnonymous });
198
- if (!credits) return false;
199
- return credits.credits > 0;
151
+ export const useHasCredits = (): boolean => {
152
+ const { hasCredits } = useCredits();
153
+ return hasCredits;
200
154
  };
@@ -1,10 +1,8 @@
1
1
  /**
2
2
  * usePremium Hook
3
- * Complete subscription management for 100+ apps
4
- * Works for both authenticated and anonymous users
5
3
  *
6
- * IMPORTANT: isPremium is based on actual RevenueCat subscription status,
7
- * NOT on whether credits document exists.
4
+ * Complete subscription management.
5
+ * Auth info automatically read from @umituz/react-native-auth.
8
6
  */
9
7
 
10
8
  import { useCallback } from 'react';
@@ -13,89 +11,50 @@ import type { UserCredits } from '../../domain/entities/Credits';
13
11
  import { useCredits } from './useCredits';
14
12
  import { useSubscriptionStatus } from './useSubscriptionStatus';
15
13
  import {
16
- useSubscriptionPackages,
17
- usePurchasePackage,
18
- useRestorePurchase,
14
+ useSubscriptionPackages,
15
+ usePurchasePackage,
16
+ useRestorePurchase,
19
17
  } from '../../revenuecat/presentation/hooks/useSubscriptionQueries';
20
18
  import { usePaywallVisibility } from './usePaywallVisibility';
21
19
 
20
+ declare const __DEV__: boolean;
21
+
22
22
  export interface UsePremiumResult {
23
- /** User has active premium subscription */
24
23
  isPremium: boolean;
25
- /** Loading credits or packages */
26
24
  isLoading: boolean;
27
- /** Available subscription packages */
28
25
  packages: PurchasesPackage[];
29
- /** User's credits (null if not premium) */
30
26
  credits: UserCredits | null;
31
- /** Paywall visibility state */
32
27
  showPaywall: boolean;
33
- /** Purchase a subscription package */
34
28
  purchasePackage: (pkg: PurchasesPackage) => Promise<boolean>;
35
- /** Restore previous purchases */
36
29
  restorePurchase: () => Promise<boolean>;
37
- /** Set paywall visibility */
38
30
  setShowPaywall: (show: boolean) => void;
39
- /** Close paywall */
40
31
  closePaywall: () => void;
41
- /** Open paywall */
42
32
  openPaywall: () => void;
43
33
  }
44
34
 
45
- /**
46
- * Complete premium subscription management
47
- *
48
- * @param userId - User ID (undefined for anonymous users)
49
- * @returns Premium status, packages, and subscription actions
50
- *
51
- * @example
52
- * ```typescript
53
- * const { isPremium, packages, purchasePackage } = usePremium(userId);
54
- * ```
55
- */
56
- export const usePremium = (userId?: string): UsePremiumResult => {
57
- // Fetch real subscription status from RevenueCat
58
- const { isPremium: subscriptionActive, isLoading: statusLoading } =
59
- useSubscriptionStatus({
60
- userId,
61
- enabled: !!userId,
62
- });
63
-
64
- // Fetch user credits (server state)
65
- const { credits, isLoading: creditsLoading } = useCredits({
66
- userId,
67
- enabled: !!userId,
68
- });
35
+ export const usePremium = (): UsePremiumResult => {
36
+ const { isPremium: subscriptionActive, isLoading: statusLoading } = useSubscriptionStatus();
37
+ const { credits, isLoading: creditsLoading } = useCredits();
69
38
 
70
- // Fetch subscription packages (works for anonymous too)
71
- const { data: packages = [], isLoading: packagesLoading } =
72
- useSubscriptionPackages(userId);
39
+ const { data: packages = [], isLoading: packagesLoading } = useSubscriptionPackages();
73
40
 
74
- // Purchase and restore mutations
75
- const purchaseMutation = usePurchasePackage(userId);
76
- const restoreMutation = useRestorePurchase(userId);
41
+ const purchaseMutation = usePurchasePackage();
42
+ const restoreMutation = useRestorePurchase();
77
43
 
78
- // Paywall visibility state
79
- const { showPaywall, setShowPaywall, closePaywall, openPaywall } =
80
- usePaywallVisibility();
44
+ const { showPaywall, setShowPaywall, closePaywall, openPaywall } = usePaywallVisibility();
81
45
 
82
- // Premium status = actual subscription status from RevenueCat
83
46
  const isPremium = subscriptionActive;
84
47
 
85
- // Purchase handler with proper error handling
86
48
  const handlePurchase = useCallback(
87
49
  async (pkg: PurchasesPackage): Promise<boolean> => {
88
- if (__DEV__) {
89
- console.log("[usePremium] handlePurchase called:", pkg.product.identifier);
50
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
51
+ console.log("[usePremium] handlePurchase:", pkg.product.identifier);
90
52
  }
91
53
  try {
92
54
  const result = await purchaseMutation.mutateAsync(pkg);
93
- if (__DEV__) {
94
- console.log("[usePremium] Purchase result:", { success: result.success });
95
- }
96
55
  return result.success;
97
56
  } catch (error) {
98
- if (__DEV__) {
57
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
99
58
  console.error("[usePremium] Purchase failed:", error);
100
59
  }
101
60
  return false;
@@ -104,13 +63,12 @@ export const usePremium = (userId?: string): UsePremiumResult => {
104
63
  [purchaseMutation],
105
64
  );
106
65
 
107
- // Restore handler with proper error handling
108
66
  const handleRestore = useCallback(async (): Promise<boolean> => {
109
67
  try {
110
68
  const result = await restoreMutation.mutateAsync();
111
69
  return result.success;
112
70
  } catch (error) {
113
- if (__DEV__) {
71
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
114
72
  console.error("[usePremium] Restore failed:", error);
115
73
  }
116
74
  return false;
@@ -4,6 +4,11 @@
4
4
  */
5
5
 
6
6
  import { useEffect, useRef } from "react";
7
+ import {
8
+ useAuthStore,
9
+ selectUserId,
10
+ selectIsAnonymous,
11
+ } from "@umituz/react-native-auth";
7
12
  import { getSavedPurchase, clearSavedPurchase } from "./useAuthAwarePurchase";
8
13
  import { usePremium } from "./usePremium";
9
14
  import { SubscriptionManager } from "../../revenuecat";
@@ -12,8 +17,6 @@ import { usePurchaseLoadingStore } from "../stores";
12
17
  declare const __DEV__: boolean;
13
18
 
14
19
  export interface UseSavedPurchaseAutoExecutionParams {
15
- userId?: string;
16
- isAnonymous?: boolean;
17
20
  onSuccess?: () => void;
18
21
  onError?: (error: Error) => void;
19
22
  }
@@ -23,24 +26,26 @@ export interface UseSavedPurchaseAutoExecutionResult {
23
26
  }
24
27
 
25
28
  export const useSavedPurchaseAutoExecution = (
26
- params: UseSavedPurchaseAutoExecutionParams
29
+ params?: UseSavedPurchaseAutoExecutionParams
27
30
  ): UseSavedPurchaseAutoExecutionResult => {
28
- const { userId, isAnonymous, onSuccess, onError } = params;
29
- const { purchasePackage } = usePremium(userId);
31
+ const { onSuccess, onError } = params ?? {};
32
+
33
+ const userId = useAuthStore(selectUserId);
34
+ const isAnonymous = useAuthStore(selectIsAnonymous);
35
+
36
+ const { purchasePackage } = usePremium();
30
37
  const { startPurchase, endPurchase } = usePurchaseLoadingStore();
31
38
 
32
39
  const prevIsAnonymousRef = useRef<boolean | undefined>(undefined);
33
40
  const isExecutingRef = useRef(false);
34
41
  const hasExecutedRef = useRef(false);
35
42
 
36
- // Store callbacks in refs to avoid dependency changes
37
43
  const purchasePackageRef = useRef(purchasePackage);
38
44
  const onSuccessRef = useRef(onSuccess);
39
45
  const onErrorRef = useRef(onError);
40
46
  const startPurchaseRef = useRef(startPurchase);
41
47
  const endPurchaseRef = useRef(endPurchase);
42
48
 
43
- // Update refs when values change
44
49
  useEffect(() => {
45
50
  purchasePackageRef.current = purchasePackage;
46
51
  }, [purchasePackage]);
@@ -69,10 +74,9 @@ export const useSavedPurchaseAutoExecution = (
69
74
  const wasAnonymous = prevIsAnonymous === true;
70
75
  const becameAuthenticated = wasAnonymous && isAuthenticated;
71
76
 
72
- // Only log when there's a state change worth noting
73
77
  const shouldLog = prevIsAnonymousRef.current !== isAnonymous;
74
78
 
75
- if (__DEV__ && shouldLog) {
79
+ if (typeof __DEV__ !== "undefined" && __DEV__ && shouldLog) {
76
80
  console.log("[SavedPurchaseAutoExecution] Auth state check:", {
77
81
  userId: userId?.slice(0, 8),
78
82
  prevIsAnonymous,
@@ -90,21 +94,15 @@ export const useSavedPurchaseAutoExecution = (
90
94
  });
91
95
  }
92
96
 
93
- // Execute only once when transitioning from anonymous to authenticated
94
97
  if (
95
98
  becameAuthenticated &&
96
99
  savedPurchase &&
97
100
  !isExecutingRef.current &&
98
101
  !hasExecutedRef.current
99
102
  ) {
100
- if (__DEV__) {
101
- console.log("[SavedPurchaseAutoExecution] Triggering auto-execution...");
102
- }
103
-
104
103
  hasExecutedRef.current = true;
105
104
  isExecutingRef.current = true;
106
105
 
107
- // Execute purchase flow
108
106
  const executeFlow = async () => {
109
107
  const currentUserId = userId;
110
108
  if (!currentUserId) {
@@ -112,75 +110,31 @@ export const useSavedPurchaseAutoExecution = (
112
110
  return;
113
111
  }
114
112
 
115
- if (__DEV__) {
116
- console.log(
117
- "[SavedPurchaseAutoExecution] Waiting for RevenueCat initialization...",
118
- {
119
- userId: currentUserId.slice(0, 8),
120
- productId: savedPurchase.pkg.product.identifier,
121
- }
122
- );
123
- }
124
-
125
113
  const maxAttempts = 20;
126
114
  const delayMs = 500;
127
115
 
128
116
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
129
117
  const isReady = SubscriptionManager.isInitializedForUser(currentUserId);
130
118
 
131
- if (__DEV__) {
132
- console.log(
133
- `[SavedPurchaseAutoExecution] Attempt ${attempt + 1}/${maxAttempts}, isReady: ${isReady}`
134
- );
135
- }
136
-
137
119
  if (isReady) {
138
120
  const pkg = savedPurchase.pkg;
139
121
  clearSavedPurchase();
140
122
 
141
- if (__DEV__) {
142
- console.log(
143
- "[SavedPurchaseAutoExecution] RevenueCat ready, starting purchase...",
144
- { productId: pkg.product.identifier, userId: currentUserId.slice(0, 8) }
145
- );
146
- }
147
-
148
123
  startPurchaseRef.current(pkg.product.identifier, "auto-execution");
149
124
 
150
125
  try {
151
- if (__DEV__) {
152
- console.log("[SavedPurchaseAutoExecution] Calling purchasePackage...");
153
- }
154
-
155
126
  const success = await purchasePackageRef.current(pkg);
156
127
 
157
- if (__DEV__) {
158
- console.log("[SavedPurchaseAutoExecution] Purchase completed:", {
159
- success,
160
- productId: pkg.product.identifier,
161
- });
162
- }
163
-
164
128
  if (success && onSuccessRef.current) {
165
129
  onSuccessRef.current();
166
130
  }
167
131
  } catch (error) {
168
- if (__DEV__) {
169
- console.error("[SavedPurchaseAutoExecution] Purchase error:", {
170
- error,
171
- productId: pkg.product.identifier,
172
- });
173
- }
174
132
  if (onErrorRef.current && error instanceof Error) {
175
133
  onErrorRef.current(error);
176
134
  }
177
135
  } finally {
178
136
  endPurchaseRef.current();
179
137
  isExecutingRef.current = false;
180
-
181
- if (__DEV__) {
182
- console.log("[SavedPurchaseAutoExecution] Purchase flow finished");
183
- }
184
138
  }
185
139
 
186
140
  return;
@@ -189,11 +143,6 @@ export const useSavedPurchaseAutoExecution = (
189
143
  await new Promise((resolve) => setTimeout(resolve, delayMs));
190
144
  }
191
145
 
192
- if (__DEV__) {
193
- console.log(
194
- "[SavedPurchaseAutoExecution] Timeout waiting for RevenueCat, clearing saved purchase"
195
- );
196
- }
197
146
  clearSavedPurchase();
198
147
  isExecutingRef.current = false;
199
148
  };
@@ -1,7 +1,6 @@
1
1
  /**
2
2
  * useSubscriptionSettingsConfig Hook
3
3
  * Returns ready-to-use config for settings screens
4
- * Single Source of Truth: Firestore (credits document)
5
4
  */
6
5
 
7
6
  import { useMemo, useCallback } from "react";
@@ -24,58 +23,43 @@ export type {
24
23
  UseSubscriptionSettingsConfigParams,
25
24
  } from "../types/SubscriptionSettingsTypes";
26
25
 
27
- /**
28
- * Hook that returns ready-to-use subscription config for settings
29
- * Single Source of Truth: Firestore credits document
30
- */
31
26
  export const useSubscriptionSettingsConfig = (
32
- params: UseSubscriptionSettingsConfigParams
27
+ params: Omit<UseSubscriptionSettingsConfigParams, 'userId'>
33
28
  ): SubscriptionSettingsConfig => {
34
- const { userId, translations, creditLimit, upgradePrompt } = params;
29
+ const { translations, creditLimit, upgradePrompt } = params;
35
30
 
36
- // Single Source of Truth: Firestore credits document
37
- const { credits } = useCredits({ userId, enabled: !!userId });
31
+ const { credits } = useCredits();
38
32
  const { openPaywall } = usePaywallVisibility();
39
33
 
40
34
  const handleOpenPaywall = useCallback(() => {
41
35
  openPaywall("settings");
42
36
  }, [openPaywall]);
43
37
 
44
- // All data from Firestore (Single Source of Truth)
45
38
  const isPremium = credits?.isPremium ?? false;
46
39
  const willRenew = credits?.willRenew ?? false;
47
40
 
48
- // Expiration date from Firestore
49
41
  const expiresAtIso = credits?.expirationDate?.toISOString() ?? null;
50
-
51
- // Purchase date from Firestore
52
42
  const purchasedAtIso = credits?.purchasedAt?.toISOString() ?? null;
53
43
 
54
- // Credit limit from Firestore or config fallback
55
44
  const dynamicCreditLimit = useMemo(() => {
56
45
  if (credits?.creditLimit) return credits.creditLimit;
57
46
  const config = getCreditsConfig();
58
47
  return creditLimit ?? config.creditLimit;
59
48
  }, [credits?.creditLimit, creditLimit]);
60
49
 
61
- // Formatted dates
62
50
  const formattedExpirationDate = useMemo(() => formatDate(expiresAtIso), [expiresAtIso]);
63
51
  const formattedPurchaseDate = useMemo(() => formatDate(purchasedAtIso), [purchasedAtIso]);
64
52
 
65
- // Days remaining
66
53
  const daysRemaining = useMemo(() => calculateDaysRemaining(expiresAtIso), [expiresAtIso]);
67
54
 
68
- // Period type from Firestore
69
55
  const periodType = credits?.periodType;
70
56
 
71
- // Status type: prioritize Firestore status, then derive from willRenew + expiration + periodType
72
57
  const statusType: SubscriptionStatusType = credits?.status
73
58
  ? (credits.status as SubscriptionStatusType)
74
59
  : getSubscriptionStatusType(isPremium, willRenew, expiresAtIso, periodType);
75
60
 
76
61
  const creditsArray = useCreditsArray(credits, dynamicCreditLimit, translations);
77
62
 
78
- // Centralized display flags
79
63
  const hasCredits = creditsArray.length > 0;
80
64
  const display = useMemo(() => ({
81
65
  showHeader: isPremium || hasCredits,
@@ -84,7 +68,6 @@ export const useSubscriptionSettingsConfig = (
84
68
  showExpirationDate: (isPremium || hasCredits) && !!expiresAtIso,
85
69
  }), [isPremium, hasCredits, upgradePrompt, expiresAtIso]);
86
70
 
87
- // Build config
88
71
  return useMemo((): SubscriptionSettingsConfig => ({
89
72
  enabled: true,
90
73
  settingsItem: {
@@ -1,11 +1,15 @@
1
1
  /**
2
2
  * useSubscriptionStatus Hook
3
3
  *
4
- * TanStack Query hook for checking real subscription status from RevenueCat.
5
- * This provides the actual premium status based on entitlements, not credits.
4
+ * Checks real subscription status from RevenueCat.
5
+ * Auth info automatically read from @umituz/react-native-auth.
6
6
  */
7
7
 
8
8
  import { useQuery } from "@umituz/react-native-design-system";
9
+ import {
10
+ useAuthStore,
11
+ selectUserId,
12
+ } from "@umituz/react-native-auth";
9
13
  import { SubscriptionManager } from "../../revenuecat/infrastructure/managers/SubscriptionManager";
10
14
 
11
15
  export const subscriptionStatusQueryKeys = {
@@ -21,34 +25,20 @@ export interface SubscriptionStatusResult {
21
25
  refetch: () => void;
22
26
  }
23
27
 
24
- export interface UseSubscriptionStatusParams {
25
- userId: string | undefined;
26
- enabled?: boolean;
27
- }
28
+ export const useSubscriptionStatus = (): SubscriptionStatusResult => {
29
+ const userId = useAuthStore(selectUserId);
28
30
 
29
- /**
30
- * Check real subscription status from RevenueCat
31
- *
32
- * @param userId - User ID
33
- * @param enabled - Whether to enable the query
34
- * @returns Subscription status with isPremium flag
35
- */
36
- export const useSubscriptionStatus = ({
37
- userId,
38
- enabled = true,
39
- }: UseSubscriptionStatusParams): SubscriptionStatusResult => {
40
31
  const { data, isLoading, error, refetch } = useQuery({
41
32
  queryKey: subscriptionStatusQueryKeys.user(userId ?? ""),
42
33
  queryFn: async () => {
43
34
  if (!userId) {
44
35
  return { isPremium: false, expirationDate: null };
45
36
  }
46
-
47
37
  return SubscriptionManager.checkPremiumStatus();
48
38
  },
49
- enabled: enabled && !!userId && SubscriptionManager.isInitializedForUser(userId),
50
- staleTime: 0, // No cache - always fetch fresh premium status
51
- gcTime: 0, // Don't cache - garbage collect immediately
39
+ enabled: !!userId && SubscriptionManager.isInitializedForUser(userId),
40
+ staleTime: 0,
41
+ gcTime: 0,
52
42
  refetchOnMount: true,
53
43
  refetchOnWindowFocus: true,
54
44
  refetchOnReconnect: true,
@@ -2,11 +2,16 @@
2
2
  * Purchase Package Hook
3
3
  * TanStack mutation for purchasing subscription packages
4
4
  * Credits are initialized by CustomerInfoListener (not here to avoid duplicates)
5
+ * Auth info automatically read from @umituz/react-native-auth
5
6
  */
6
7
 
7
8
  import { useMutation, useQueryClient } from "@umituz/react-native-design-system";
8
9
  import type { PurchasesPackage } from "react-native-purchases";
9
10
  import { useAlert } from "@umituz/react-native-design-system";
11
+ import {
12
+ useAuthStore,
13
+ selectUserId,
14
+ } from "@umituz/react-native-auth";
10
15
  import { SubscriptionManager } from "../../infrastructure/managers/SubscriptionManager";
11
16
  import { SUBSCRIPTION_QUERY_KEYS } from "./subscriptionQueryKeys";
12
17
  import { creditsQueryKeys } from "../../../presentation/hooks/useCredits";
@@ -23,8 +28,10 @@ export interface PurchaseMutationResult {
23
28
  /**
24
29
  * Purchase a subscription package
25
30
  * Credits are initialized by CustomerInfoListener when entitlement becomes active
31
+ * Auth info automatically read from auth store
26
32
  */
27
- export const usePurchasePackage = (userId: string | undefined) => {
33
+ export const usePurchasePackage = () => {
34
+ const userId = useAuthStore(selectUserId);
28
35
  const queryClient = useQueryClient();
29
36
  const { showSuccess, showError } = useAlert();
30
37
 
@@ -2,9 +2,14 @@
2
2
  * Restore Purchase Hook
3
3
  * TanStack mutation for restoring previous purchases
4
4
  * Credits are initialized by CustomerInfoListener (not here to avoid duplicates)
5
+ * Auth info automatically read from @umituz/react-native-auth
5
6
  */
6
7
 
7
8
  import { useMutation, useQueryClient } from "@umituz/react-native-design-system";
9
+ import {
10
+ useAuthStore,
11
+ selectUserId,
12
+ } from "@umituz/react-native-auth";
8
13
  import { SubscriptionManager } from "../../infrastructure/managers/SubscriptionManager";
9
14
  import { SUBSCRIPTION_QUERY_KEYS } from "./subscriptionQueryKeys";
10
15
  import { creditsQueryKeys } from "../../../presentation/hooks/useCredits";
@@ -17,8 +22,10 @@ interface RestoreResult {
17
22
  /**
18
23
  * Restore previous purchases
19
24
  * Credits are initialized by CustomerInfoListener when entitlement becomes active
25
+ * Auth info automatically read from auth store
20
26
  */
21
- export const useRestorePurchase = (userId: string | undefined) => {
27
+ export const useRestorePurchase = () => {
28
+ const userId = useAuthStore(selectUserId);
22
29
  const queryClient = useQueryClient();
23
30
 
24
31
  return useMutation({
@@ -1,9 +1,14 @@
1
1
  /**
2
2
  * Subscription Packages Hook
3
3
  * TanStack query for fetching available packages
4
+ * Auth info automatically read from @umituz/react-native-auth
4
5
  */
5
6
 
6
7
  import { useQuery } from "@umituz/react-native-design-system";
8
+ import {
9
+ useAuthStore,
10
+ selectUserId,
11
+ } from "@umituz/react-native-auth";
7
12
  import { SubscriptionManager } from '../../infrastructure/managers/SubscriptionManager';
8
13
  import {
9
14
  SUBSCRIPTION_QUERY_KEYS,
@@ -14,8 +19,10 @@ import {
14
19
  /**
15
20
  * Fetch available subscription packages
16
21
  * Works for both authenticated and anonymous users
22
+ * Auth info automatically read from auth store
17
23
  */
18
- export const useSubscriptionPackages = (userId: string | undefined) => {
24
+ export const useSubscriptionPackages = () => {
25
+ const userId = useAuthStore(selectUserId);
19
26
  const isConfigured = SubscriptionManager.isConfigured();
20
27
 
21
28
  return useQuery({