@umituz/react-native-subscription 2.35.16 → 2.35.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/package.json +1 -1
  2. package/src/domains/config/utils/planSelectors.ts +1 -1
  3. package/src/domains/credits/presentation/useCredits.ts +6 -20
  4. package/src/domains/paywall/hooks/usePaywallActions.ts +2 -82
  5. package/src/domains/revenuecat/core/customerInfoHelpers.ts +21 -0
  6. package/src/domains/subscription/application/SubscriptionAuthListener.ts +0 -19
  7. package/src/domains/subscription/application/initializer/BackgroundInitializer.ts +2 -8
  8. package/src/domains/subscription/application/statusChangeHandlers.ts +0 -30
  9. package/src/domains/subscription/constants/thresholds.ts +10 -0
  10. package/src/domains/subscription/infrastructure/handlers/PurchaseStatusResolver.ts +3 -3
  11. package/src/domains/subscription/infrastructure/handlers/package-operations/PackageFetcher.ts +0 -19
  12. package/src/domains/subscription/infrastructure/hooks/customer-info/useCustomerInfo.ts +1 -1
  13. package/src/domains/subscription/infrastructure/hooks/useInitializeSubscription.ts +2 -4
  14. package/src/domains/subscription/infrastructure/hooks/usePurchasePackage.ts +0 -44
  15. package/src/domains/subscription/infrastructure/services/CustomerInfoListenerManager.ts +1 -31
  16. package/src/domains/subscription/infrastructure/services/OfferingsFetcher.ts +0 -21
  17. package/src/domains/subscription/infrastructure/services/listeners/CustomerInfoHandler.ts +6 -36
  18. package/src/domains/subscription/infrastructure/services/purchase/PurchaseExecutor.ts +0 -6
  19. package/src/domains/subscription/infrastructure/utils/PremiumStatusSyncer.ts +3 -44
  20. package/src/domains/subscription/presentation/featureGateActions.ts +0 -37
  21. package/src/domains/subscription/presentation/screens/components/SubscriptionHeader.tsx +1 -1
  22. package/src/domains/subscription/presentation/screens/components/SubscriptionHeaderContent.tsx +1 -1
  23. package/src/domains/subscription/presentation/useAuthAwarePurchase.ts +0 -43
  24. package/src/domains/subscription/presentation/useFeatureGate.ts +0 -39
  25. package/src/domains/subscription/presentation/useSubscriptionStatus.ts +6 -20
  26. package/src/domains/subscription/utils/authGuards.ts +26 -2
  27. package/src/domains/subscription/utils/expirationHelpers.ts +2 -2
  28. package/src/domains/wallet/presentation/hooks/useProductMetadata.ts +3 -6
  29. package/src/domains/wallet/presentation/hooks/useTransactionHistory.ts +3 -6
  30. package/src/shared/infrastructure/react-query/hooks/usePreviousUserCleanup.ts +39 -0
  31. package/src/shared/infrastructure/react-query/queryConfig.ts +22 -0
  32. package/src/shared/infrastructure/react-query/queryInvalidation.ts +46 -0
  33. package/src/shared/presentation/hooks/useServiceCall.ts +2 -1
  34. package/src/shared/utils/errorUtils.ts +32 -0
  35. package/src/utils/appUtils.ts +6 -0
  36. package/src/domains/subscription/presentation/components/details/PremiumDetailsCard.constants.ts +0 -1
  37. package/src/domains/subscription/presentation/screens/components/SubscriptionHeader.constants.ts +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-subscription",
3
- "version": "2.35.16",
3
+ "version": "2.35.18",
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,7 +33,7 @@ export const getCreditLimitForPlan = (
33
33
  ): number => {
34
34
  const plan = getPlanById(config, planId);
35
35
  if (!plan) {
36
- console.warn(`[planSelectors] Plan not found: ${planId}, returning 0`);
36
+
37
37
  return 0;
38
38
  }
39
39
  return plan.credits;
@@ -1,7 +1,9 @@
1
1
  import { useQuery, useQueryClient } from "@umituz/react-native-design-system";
2
- import { useCallback, useMemo, useEffect, useRef } from "react";
2
+ import { useCallback, useMemo, useEffect } from "react";
3
3
  import { useAuthStore, selectUserId } from "@umituz/react-native-auth";
4
4
  import { subscriptionEventBus, SUBSCRIPTION_EVENTS } from "../../../shared/infrastructure/SubscriptionEventBus";
5
+ import { NO_CACHE_QUERY_CONFIG } from "../../../shared/infrastructure/react-query/queryConfig";
6
+ import { usePreviousUserCleanup } from "../../../shared/infrastructure/react-query/hooks/usePreviousUserCleanup";
5
7
  import {
6
8
  getCreditsRepository,
7
9
  getCreditsConfig,
@@ -44,29 +46,13 @@ export const useCredits = (): UseCreditsResult => {
44
46
  return result.data ?? null;
45
47
  },
46
48
  enabled: queryEnabled,
47
- gcTime: 0,
48
- staleTime: 0,
49
- refetchOnMount: "always",
50
- refetchOnWindowFocus: "always",
51
- refetchOnReconnect: "always",
49
+ ...NO_CACHE_QUERY_CONFIG,
52
50
  });
53
51
 
54
52
  const queryClient = useQueryClient();
55
53
 
56
- // Track previous userId to clear stale cache on logout/user switch
57
- const prevUserIdRef = useRef(userId);
58
-
59
- useEffect(() => {
60
- const prevUserId = prevUserIdRef.current;
61
- prevUserIdRef.current = userId;
62
-
63
- // Clear previous user's cache when userId changes (logout or user switch)
64
- if (prevUserId !== userId && isAuthenticated(prevUserId)) {
65
- queryClient.removeQueries({
66
- queryKey: creditsQueryKeys.user(prevUserId),
67
- });
68
- }
69
- }, [userId, queryClient]);
54
+ // Clean up previous user's cache on logout/user switch
55
+ usePreviousUserCleanup(userId, queryClient, creditsQueryKeys.user);
70
56
 
71
57
  useEffect(() => {
72
58
  if (!isAuthenticated(userId)) return undefined;
@@ -7,8 +7,6 @@ import type { PurchasesPackage } from "react-native-purchases";
7
7
  import { usePurchaseLoadingStore } from "../../subscription/presentation/stores";
8
8
  import type { PurchaseSource } from "../../subscription/core/SubscriptionConstants";
9
9
 
10
- declare const __DEV__: boolean;
11
-
12
10
  export interface UsePaywallActionsParams {
13
11
  packages?: PurchasesPackage[];
14
12
  onPurchase?: (pkg: PurchasesPackage) => Promise<void | boolean>;
@@ -54,35 +52,18 @@ export function usePaywallActions({
54
52
  });
55
53
 
56
54
  const handlePurchase = useCallback(async () => {
57
- if (typeof __DEV__ !== "undefined" && __DEV__) {
58
- console.log("[usePaywallActions] handlePurchase called", {
59
- selectedPlanId,
60
- hasOnPurchase: !!onPurchaseRef.current,
61
- isProcessing,
62
- packagesCount: packages.length,
63
- });
64
- }
65
55
 
66
56
  if (!selectedPlanId) {
67
- if (typeof __DEV__ !== "undefined" && __DEV__) {
68
- console.warn("[usePaywallActions] ❌ No plan selected");
69
- }
70
57
  return;
71
58
  }
72
59
 
73
60
  if (!onPurchaseRef.current) {
74
- if (typeof __DEV__ !== "undefined" && __DEV__) {
75
- console.error("[usePaywallActions] ❌ No onPurchase callback provided");
76
- }
77
61
  const err = new Error("Purchase handler not configured");
78
62
  onPurchaseErrorRef.current?.(err);
79
63
  return;
80
64
  }
81
65
 
82
66
  if (isProcessing) {
83
- if (typeof __DEV__ !== "undefined" && __DEV__) {
84
- console.warn("[usePaywallActions] ⚠️ Already processing, ignoring duplicate request");
85
- }
86
67
  return;
87
68
  }
88
69
 
@@ -100,117 +81,56 @@ export function usePaywallActions({
100
81
  return;
101
82
  }
102
83
 
103
- if (typeof __DEV__ !== "undefined" && __DEV__) {
104
- console.log("[usePaywallActions] ✅ Starting purchase", {
105
- productId: pkg.product.identifier,
106
- title: pkg.product.title,
107
- });
108
- }
109
-
110
84
  setIsLocalProcessing(true);
111
85
  startPurchase(selectedPlanId, "manual");
112
86
 
113
87
  try {
114
- if (typeof __DEV__ !== "undefined" && __DEV__) {
115
- console.log("[usePaywallActions] 🚀 Calling onPurchase callback");
116
- }
117
88
 
118
89
  const success = await onPurchaseRef.current(pkg);
119
90
 
120
- if (typeof __DEV__ !== "undefined" && __DEV__) {
121
- console.log("[usePaywallActions] 📦 Purchase result:", { success, type: typeof success });
122
- }
123
-
124
91
  if (success === true) {
125
- if (typeof __DEV__ !== "undefined" && __DEV__) {
126
- console.log("[usePaywallActions] ✅ Purchase successful, calling success callbacks");
127
- }
128
92
  onPurchaseSuccessRef.current?.();
129
93
  onCloseRef.current?.();
130
94
  } else if (success === false) {
131
95
  if (typeof __DEV__ !== "undefined" && __DEV__) {
132
96
  console.warn("[usePaywallActions] ⚠️ Purchase returned false (user cancelled or failed)");
133
97
  }
134
- } else {
135
- if (typeof __DEV__ !== "undefined" && __DEV__) {
136
- console.error("[usePaywallActions] ❌ Purchase returned unexpected value:", success);
137
- }
138
98
  }
99
+ // else: success is undefined/null - no action needed
139
100
  } catch (error) {
140
- if (typeof __DEV__ !== "undefined" && __DEV__) {
141
- console.error("[usePaywallActions] ❌ Purchase error:", error);
142
- }
143
101
  const err = error instanceof Error ? error : new Error(String(error));
144
102
  onPurchaseErrorRef.current?.(err);
145
103
  } finally {
146
- if (typeof __DEV__ !== "undefined" && __DEV__) {
147
- console.log("[usePaywallActions] 🏁 Purchase completed, cleaning up");
148
- }
149
104
  setIsLocalProcessing(false);
150
105
  endPurchase(selectedPlanId);
151
106
  }
152
107
  }, [selectedPlanId, packages, isProcessing, startPurchase, endPurchase]);
153
108
 
154
109
  const handleRestore = useCallback(async () => {
155
- if (typeof __DEV__ !== "undefined" && __DEV__) {
156
- console.log("[usePaywallActions] handleRestore called", {
157
- hasOnRestore: !!onRestoreRef.current,
158
- isProcessing,
159
- });
160
- }
161
110
 
162
111
  if (!onRestoreRef.current) {
163
- if (typeof __DEV__ !== "undefined" && __DEV__) {
164
- console.error("[usePaywallActions] ❌ No onRestore callback provided");
165
- }
166
112
  const err = new Error("Restore handler not configured");
167
113
  onPurchaseErrorRef.current?.(err);
168
114
  return;
169
115
  }
170
116
 
171
117
  if (isProcessing) {
172
- if (typeof __DEV__ !== "undefined" && __DEV__) {
173
- console.warn("[usePaywallActions] ⚠️ Already processing, ignoring restore request");
174
- }
175
118
  return;
176
119
  }
177
120
 
178
121
  setIsLocalProcessing(true);
179
122
  try {
180
- if (typeof __DEV__ !== "undefined" && __DEV__) {
181
- console.log("[usePaywallActions] 🚀 Calling onRestore callback");
182
- }
183
123
 
184
124
  const success = await onRestoreRef.current();
185
125
 
186
- if (typeof __DEV__ !== "undefined" && __DEV__) {
187
- console.log("[usePaywallActions] 📦 Restore result:", { success, type: typeof success });
188
- }
189
-
190
126
  if (success === true) {
191
- if (typeof __DEV__ !== "undefined" && __DEV__) {
192
- console.log("[usePaywallActions] ✅ Restore successful");
193
- }
194
127
  onPurchaseSuccessRef.current?.();
195
- } else if (success === false) {
196
- if (typeof __DEV__ !== "undefined" && __DEV__) {
197
- console.warn("[usePaywallActions] ⚠️ Restore returned false");
198
- }
199
- } else {
200
- if (typeof __DEV__ !== "undefined" && __DEV__) {
201
- console.error("[usePaywallActions] ❌ Restore returned unexpected value:", success);
202
- }
203
128
  }
129
+ // else: success is false/undefined - restore failed or user cancelled, no action needed
204
130
  } catch (error) {
205
- if (typeof __DEV__ !== "undefined" && __DEV__) {
206
- console.error("[usePaywallActions] ❌ Restore error:", error);
207
- }
208
131
  const err = error instanceof Error ? error : new Error(String(error));
209
132
  onPurchaseErrorRef.current?.(err);
210
133
  } finally {
211
- if (typeof __DEV__ !== "undefined" && __DEV__) {
212
- console.log("[usePaywallActions] 🏁 Restore completed");
213
- }
214
134
  setIsLocalProcessing(false);
215
135
  }
216
136
  }, [isProcessing]);
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Customer Info Helper Functions
3
+ * Utilities for extracting data from RevenueCat CustomerInfo objects
4
+ */
5
+
6
+ import type { CustomerInfo } from "react-native-purchases";
7
+
8
+ /**
9
+ * Extracts active entitlement IDs from CustomerInfo
10
+ * Useful for logging and debugging
11
+ *
12
+ * @param customerInfo - RevenueCat CustomerInfo object
13
+ * @returns Array of active entitlement IDs
14
+ *
15
+ * @example
16
+ * const activeIds = getActiveEntitlementIds(customerInfo);
17
+ * console.log("Active entitlements:", activeIds); // ["premium", "pro_features"]
18
+ */
19
+ export function getActiveEntitlementIds(customerInfo: CustomerInfo): string[] {
20
+ return Object.keys(customerInfo.entitlements.active);
21
+ }
@@ -1,7 +1,5 @@
1
1
  import type { FirebaseAuthLike } from "./SubscriptionInitializerTypes";
2
2
 
3
- declare const __DEV__: boolean;
4
-
5
3
  /**
6
4
  * Gets the current user ID from Firebase auth.
7
5
  * Returns undefined for anonymous users to let RevenueCat generate its own anonymous ID.
@@ -9,17 +7,11 @@ declare const __DEV__: boolean;
9
7
  export const getCurrentUserId = (getAuth: () => FirebaseAuthLike | null): string | undefined => {
10
8
  const auth = getAuth();
11
9
  if (!auth) {
12
- if (typeof __DEV__ !== 'undefined' && __DEV__) {
13
- console.log('[SubscriptionAuthListener] No auth available');
14
- }
15
10
  return undefined;
16
11
  }
17
12
 
18
13
  const user = auth.currentUser;
19
14
  if (!user) {
20
- if (typeof __DEV__ !== 'undefined' && __DEV__) {
21
- console.log('[SubscriptionAuthListener] No current user');
22
- }
23
15
  return undefined;
24
16
  }
25
17
 
@@ -30,10 +22,6 @@ export const getCurrentUserId = (getAuth: () => FirebaseAuthLike | null): string
30
22
  return undefined;
31
23
  }
32
24
 
33
- if (typeof __DEV__ !== 'undefined' && __DEV__) {
34
- console.log('[SubscriptionAuthListener] Authenticated user:', user.uid);
35
- }
36
-
37
25
  return user.uid;
38
26
  };
39
27
 
@@ -48,16 +36,9 @@ export const setupAuthStateListener = (
48
36
  ): (() => void) | null => {
49
37
  const auth = getAuth();
50
38
  if (!auth) {
51
- if (typeof __DEV__ !== 'undefined' && __DEV__) {
52
- console.log('[SubscriptionAuthListener] Cannot setup listener - no auth available');
53
- }
54
39
  return null;
55
40
  }
56
41
 
57
- if (typeof __DEV__ !== 'undefined' && __DEV__) {
58
- console.log('[SubscriptionAuthListener] Setting up auth state listener');
59
- }
60
-
61
42
  return auth.onAuthStateChanged((user) => {
62
43
  const userId = (user && !user.isAnonymous) ? user.uid : undefined;
63
44
 
@@ -2,8 +2,6 @@ import { SubscriptionManager } from "../../infrastructure/managers/SubscriptionM
2
2
  import { getCurrentUserId, setupAuthStateListener } from "../SubscriptionAuthListener";
3
3
  import type { SubscriptionInitConfig } from "../SubscriptionInitializerTypes";
4
4
 
5
- declare const __DEV__: boolean;
6
-
7
5
  export async function startBackgroundInitialization(config: SubscriptionInitConfig): Promise<() => void> {
8
6
  const initializeInBackground = async (revenueCatUserId?: string): Promise<void> => {
9
7
  if (typeof __DEV__ !== 'undefined' && __DEV__) {
@@ -17,10 +15,6 @@ export async function startBackgroundInitialization(config: SubscriptionInitConf
17
15
  throw new Error("Firebase auth is not available");
18
16
  }
19
17
 
20
- if (typeof __DEV__ !== 'undefined' && __DEV__) {
21
- console.log('[BackgroundInitializer] Starting background initialization');
22
- }
23
-
24
18
  const initialRevenueCatUserId = getCurrentUserId(() => auth);
25
19
 
26
20
  if (typeof __DEV__ !== 'undefined' && __DEV__) {
@@ -35,8 +29,8 @@ export async function startBackgroundInitialization(config: SubscriptionInitConf
35
29
  }
36
30
  try {
37
31
  await initializeInBackground(newRevenueCatUserId);
38
- } catch (error) {
39
- console.error('[BackgroundInitializer] Failed to reinitialize on auth change', { userId: newRevenueCatUserId, error });
32
+ } catch (_error) {
33
+ // Background re-initialization errors are non-critical, already logged by SubscriptionManager
40
34
  }
41
35
  });
42
36
 
@@ -6,8 +6,6 @@ import { emitCreditsUpdated } from "./syncEventEmitter";
6
6
  import { generateInitSyncId, generateStatusSyncId } from "./syncIdGenerators";
7
7
  import { NO_SUBSCRIPTION_PRODUCT_ID, DEFAULT_FREE_USER_DATA } from "./syncConstants";
8
8
 
9
- declare const __DEV__: boolean;
10
-
11
9
  export const handleExpiredSubscription = async (userId: string): Promise<void> => {
12
10
  await getCreditsRepository().syncExpiredStatus(userId);
13
11
  emitCreditsUpdated(userId);
@@ -34,16 +32,6 @@ export const handlePremiumStatusSync = async (
34
32
  willRenew: boolean,
35
33
  periodType: PeriodType | null
36
34
  ): Promise<void> => {
37
- if (typeof __DEV__ !== "undefined" && __DEV__) {
38
- console.log("[StatusChangeHandlers] handlePremiumStatusSync called:", {
39
- userId,
40
- isPremium,
41
- productId,
42
- expiresAt,
43
- willRenew,
44
- periodType,
45
- });
46
- }
47
35
 
48
36
  const revenueCatData: RevenueCatData = {
49
37
  expirationDate: expiresAt,
@@ -60,16 +48,6 @@ export const handlePremiumStatusSync = async (
60
48
 
61
49
  const statusSyncId = generateStatusSyncId(userId, isPremium);
62
50
 
63
- if (typeof __DEV__ !== "undefined" && __DEV__) {
64
- console.log("[StatusChangeHandlers] Calling initializeCredits with:", {
65
- userId,
66
- statusSyncId,
67
- productId,
68
- source: PURCHASE_SOURCE.SETTINGS,
69
- type: PURCHASE_TYPE.INITIAL,
70
- });
71
- }
72
-
73
51
  await getCreditsRepository().initializeCredits(
74
52
  userId,
75
53
  statusSyncId,
@@ -79,13 +57,5 @@ export const handlePremiumStatusSync = async (
79
57
  PURCHASE_TYPE.INITIAL
80
58
  );
81
59
 
82
- if (typeof __DEV__ !== "undefined" && __DEV__) {
83
- console.log("[StatusChangeHandlers] initializeCredits completed, emitting credits updated event");
84
- }
85
-
86
60
  emitCreditsUpdated(userId);
87
-
88
- if (typeof __DEV__ !== "undefined" && __DEV__) {
89
- console.log("[StatusChangeHandlers] ✅ handlePremiumStatusSync completed successfully");
90
- }
91
61
  };
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Subscription Threshold Constants
3
+ * Centralized threshold values for subscription UI logic
4
+ */
5
+
6
+ /**
7
+ * Number of days before expiration to show warnings
8
+ * Used across subscription UI components for consistency
9
+ */
10
+ export const EXPIRATION_WARNING_THRESHOLD_DAYS = 7;
@@ -32,15 +32,15 @@ export class PurchaseStatusResolver {
32
32
  expirationDate: toDate(entitlement.expirationDate),
33
33
  willRenew: entitlement.willRenew ?? false,
34
34
  productIdentifier,
35
- originalPurchaseDate: toDate(entitlement.originalPurchaseDate) ?? null,
36
- latestPurchaseDate: toDate(entitlement.latestPurchaseDate) ?? null,
35
+ originalPurchaseDate: toDate(entitlement.originalPurchaseDate),
36
+ latestPurchaseDate: toDate(entitlement.latestPurchaseDate),
37
37
  billingIssuesDetected: entitlement.billingIssueDetectedAt !== null && entitlement.billingIssueDetectedAt !== undefined,
38
38
  isSandbox: entitlement.isSandbox ?? false,
39
39
  periodType: entitlement.periodType ?? null,
40
40
  packageType: detectedPackageType,
41
41
  store: null,
42
42
  gracePeriodExpiresDate: null,
43
- unsubscribeDetectedAt: toDate(entitlement.unsubscribeDetectedAt) ?? null,
43
+ unsubscribeDetectedAt: toDate(entitlement.unsubscribeDetectedAt),
44
44
  };
45
45
  }
46
46
 
@@ -1,8 +1,6 @@
1
1
  import type { PurchasesPackage } from "react-native-purchases";
2
2
  import type { IRevenueCatService } from "../../../../../shared/application/ports/IRevenueCatService";
3
3
 
4
- declare const __DEV__: boolean;
5
-
6
4
  export async function fetchPackages(
7
5
  service: IRevenueCatService
8
6
  ): Promise<PurchasesPackage[]> {
@@ -13,26 +11,12 @@ export async function fetchPackages(
13
11
  try {
14
12
  const offering = await service.fetchOfferings();
15
13
 
16
- if (__DEV__) {
17
- console.log('[PackageHandler] fetchOfferings result:', {
18
- hasOffering: !!offering,
19
- offeringId: offering?.identifier,
20
- packagesCount: offering?.availablePackages?.length,
21
- });
22
- }
23
-
24
14
  if (!offering) {
25
- if (__DEV__) {
26
- console.warn('[PackageHandler] No offering returned, returning empty array');
27
- }
28
15
  return [];
29
16
  }
30
17
 
31
18
  const packages = offering.availablePackages;
32
19
  if (!packages || packages.length === 0) {
33
- if (__DEV__) {
34
- console.warn('[PackageHandler] Offering has no packages, returning empty array');
35
- }
36
20
  return [];
37
21
  }
38
22
 
@@ -45,9 +29,6 @@ export async function fetchPackages(
45
29
 
46
30
  return packages;
47
31
  } catch (error) {
48
- if (__DEV__) {
49
- console.error('[PackageHandler] Error fetching packages:', error);
50
- }
51
32
  throw new Error(
52
33
  `Failed to fetch subscription packages. ${
53
34
  error instanceof Error ? error.message : "Unknown error"
@@ -45,7 +45,7 @@ export function useCustomerInfo(): UseCustomerInfoResult {
45
45
  listenerRef.current = null;
46
46
  }
47
47
  };
48
- }, []); // fetchCustomerInfo is stable, setup listener once
48
+ }, [fetchCustomerInfo]); // fetchCustomerInfo is stable (empty deps), included for lint
49
49
 
50
50
  return {
51
51
  customerInfo,
@@ -6,6 +6,7 @@
6
6
  import { useMutation, useQueryClient } from "@umituz/react-native-design-system";
7
7
  import { SubscriptionManager } from '../../infrastructure/managers/SubscriptionManager';
8
8
  import { SUBSCRIPTION_QUERY_KEYS } from "./subscriptionQueryKeys";
9
+ import { requireAuthentication } from "../../utils/authGuards";
9
10
 
10
11
  /**
11
12
  * Initialize subscription with RevenueCat
@@ -15,10 +16,7 @@ export const useInitializeSubscription = (userId: string | undefined) => {
15
16
 
16
17
  return useMutation({
17
18
  mutationFn: async () => {
18
- if (!userId) {
19
- throw new Error("User not authenticated");
20
- }
21
-
19
+ requireAuthentication(userId);
22
20
  return SubscriptionManager.initialize(userId);
23
21
  },
24
22
  onSuccess: () => {
@@ -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,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 {