@umituz/react-native-subscription 2.37.39 → 2.37.41

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 (132) hide show
  1. package/package.json +1 -1
  2. package/src/domains/credits/application/CreditLimitCalculator.ts +1 -9
  3. package/src/domains/credits/application/DeductCreditsCommand.ts +16 -7
  4. package/src/domains/credits/application/RefundCreditsCommand.ts +1 -5
  5. package/src/domains/credits/application/credit-strategies/TrialCreditStrategy.ts +1 -5
  6. package/src/domains/credits/application/creditDocumentHelpers.ts +2 -9
  7. package/src/domains/credits/core/Credits.ts +0 -23
  8. package/src/domains/credits/core/CreditsMapper.ts +0 -6
  9. package/src/domains/credits/core/UserCreditsDocument.ts +0 -12
  10. package/src/domains/credits/infrastructure/CreditsRepositoryManager.ts +0 -21
  11. package/src/domains/credits/infrastructure/operations/CreditsWriter.ts +2 -1
  12. package/src/domains/credits/presentation/deduct-credit/useDeductCredit.ts +2 -2
  13. package/src/domains/credits/presentation/useCredits.ts +10 -9
  14. package/src/domains/paywall/components/PaywallContainer.types.ts +0 -28
  15. package/src/domains/paywall/components/PaywallModal.styles.ts +0 -4
  16. package/src/domains/paywall/entities/types.ts +0 -5
  17. package/src/domains/paywall/hooks/usePaywallActions.ts +1 -15
  18. package/src/domains/revenuecat/core/errors/RevenueCatError.ts +0 -6
  19. package/src/domains/revenuecat/core/errors/RevenueCatErrorHandler.ts +0 -24
  20. package/src/domains/revenuecat/core/errors/RevenueCatErrorMessages.ts +0 -18
  21. package/src/domains/revenuecat/core/errors/index.ts +0 -4
  22. package/src/domains/revenuecat/core/types/RevenueCatConfig.ts +3 -7
  23. package/src/domains/revenuecat/core/types/RevenueCatData.ts +4 -9
  24. package/src/domains/revenuecat/core/types/RevenueCatTypes.ts +5 -65
  25. package/src/domains/revenuecat/core/types/index.ts +0 -4
  26. package/src/domains/revenuecat/infrastructure/services/UserSwitchMutex.ts +1 -24
  27. package/src/domains/subscription/application/SubscriptionAuthListener.ts +5 -21
  28. package/src/domains/subscription/application/SubscriptionInitializerTypes.ts +1 -5
  29. package/src/domains/subscription/application/SubscriptionSyncProcessor.ts +6 -2
  30. package/src/domains/subscription/application/SubscriptionSyncService.ts +11 -2
  31. package/src/domains/subscription/application/SubscriptionSyncUtils.ts +1 -1
  32. package/src/domains/subscription/application/initializer/BackgroundInitializer.ts +15 -2
  33. package/src/domains/subscription/application/initializer/ServiceConfigurator.ts +9 -2
  34. package/src/domains/subscription/constants/thresholds.ts +0 -9
  35. package/src/domains/subscription/core/SubscriptionConstants.ts +0 -4
  36. package/src/domains/subscription/core/SubscriptionStatus.ts +11 -21
  37. package/src/domains/subscription/core/SubscriptionStatusHandlers.ts +4 -7
  38. package/src/domains/subscription/infrastructure/handlers/PurchaseStatusResolver.ts +1 -1
  39. package/src/domains/subscription/infrastructure/hooks/subscriptionQueryKeys.ts +0 -13
  40. package/src/domains/subscription/infrastructure/hooks/usePurchasePackage.ts +0 -18
  41. package/src/domains/subscription/infrastructure/hooks/useRestorePurchase.ts +3 -17
  42. package/src/domains/subscription/infrastructure/hooks/useRevenueCatTrialEligibility.ts +0 -17
  43. package/src/domains/subscription/infrastructure/hooks/useSubscriptionPackages.ts +0 -19
  44. package/src/domains/subscription/infrastructure/hooks/useSubscriptionQueries.ts +0 -6
  45. package/src/domains/subscription/infrastructure/managers/subscriptionManagerUtils.ts +0 -17
  46. package/src/domains/subscription/infrastructure/state/initializationState.ts +0 -25
  47. package/src/domains/subscription/infrastructure/utils/InitializationCache.ts +0 -21
  48. package/src/domains/subscription/infrastructure/utils/PremiumStatusSyncer.ts +2 -7
  49. package/src/domains/subscription/infrastructure/utils/authPurchaseState.ts +19 -6
  50. package/src/domains/subscription/infrastructure/utils/renewal/PackageTierComparator.ts +1 -0
  51. package/src/domains/subscription/infrastructure/utils/trialEligibilityUtils.ts +0 -18
  52. package/src/domains/subscription/presentation/components/details/PremiumDetailsCard.styles.ts +0 -5
  53. package/src/domains/subscription/presentation/components/details/PremiumDetailsCardTypes.ts +0 -5
  54. package/src/domains/subscription/presentation/components/feedback/paywallFeedbackStyles.ts +0 -5
  55. package/src/domains/subscription/presentation/stores/index.ts +0 -4
  56. package/src/domains/subscription/presentation/stores/purchaseLoadingStore.ts +0 -13
  57. package/src/domains/subscription/presentation/useAuthAwarePurchase.ts +35 -21
  58. package/src/domains/subscription/presentation/usePaywallVisibility.ts +0 -9
  59. package/src/domains/subscription/presentation/useSubscriptionStatus.ts +8 -11
  60. package/src/domains/subscription/utils/authGuards.ts +3 -0
  61. package/src/domains/trial/application/TrialService.ts +0 -9
  62. package/src/domains/trial/core/TrialTypes.ts +0 -8
  63. package/src/domains/wallet/domain/mappers/TransactionMapper.ts +0 -5
  64. package/src/domains/wallet/domain/types/transaction.types.ts +0 -7
  65. package/src/domains/wallet/index.ts +0 -7
  66. package/src/domains/wallet/infrastructure/config/walletConfig.ts +0 -11
  67. package/src/domains/wallet/presentation/hooks/useTransactionHistory.ts +6 -3
  68. package/src/domains/wallet/presentation/hooks/useWallet.ts +0 -7
  69. package/src/domains/wallet/utils/transactionIconMap.ts +0 -10
  70. package/src/global.d.ts +0 -6
  71. package/src/index.ts +1 -4
  72. package/src/init/createSubscriptionInitModule.ts +12 -2
  73. package/src/init/index.ts +1 -5
  74. package/src/presentation/hooks/feedback/useFeedbackSubmit.ts +0 -11
  75. package/src/shared/application/FeedbackService.ts +3 -21
  76. package/src/shared/application/ports/ISubscriptionRepository.ts +0 -4
  77. package/src/shared/infrastructure/SubscriptionEventBus.ts +0 -13
  78. package/src/shared/infrastructure/firestore/collectionUtils.ts +1 -17
  79. package/src/shared/infrastructure/firestore/index.ts +0 -4
  80. package/src/shared/infrastructure/firestore/resultUtils.ts +0 -12
  81. package/src/shared/infrastructure/react-query/hooks/usePreviousUserCleanup.ts +0 -17
  82. package/src/shared/infrastructure/react-query/queryConfig.ts +0 -15
  83. package/src/shared/utils/BaseError.ts +0 -5
  84. package/src/shared/utils/Result.ts +0 -20
  85. package/src/shared/utils/dateConverter.ts +6 -46
  86. package/src/utils/appUtils.ts +0 -16
  87. package/src/utils/creditMapper.ts +0 -7
  88. package/src/utils/dateUtils.compare.ts +0 -24
  89. package/src/utils/dateUtils.core.ts +0 -39
  90. package/src/utils/dateUtils.format.ts +0 -41
  91. package/src/utils/dateUtils.math.ts +0 -41
  92. package/src/utils/dateUtils.ts +0 -5
  93. package/src/utils/packagePeriodUtils.ts +0 -20
  94. package/src/utils/packageTypeDetector.ts +1 -21
  95. package/src/utils/premiumStatusUtils.ts +1 -14
  96. package/src/utils/priceUtils.ts +0 -35
  97. package/src/utils/tierUtils.ts +1 -8
  98. package/src/utils/types.ts +1 -25
  99. package/src/utils/validation.ts +1 -7
  100. package/src/domains/README.md +0 -52
  101. package/src/domains/config/domain/README.md +0 -37
  102. package/src/domains/config/domain/entities/README.md +0 -41
  103. package/src/domains/paywall/README.md +0 -101
  104. package/src/domains/paywall/entities/README.md +0 -40
  105. package/src/domains/paywall/hooks/README.md +0 -41
  106. package/src/domains/subscription/application/syncConstants.ts +0 -1
  107. package/src/domains/subscription/infrastructure/README.md +0 -41
  108. package/src/domains/subscription/infrastructure/config/README.md +0 -49
  109. package/src/domains/subscription/infrastructure/handlers/README.md +0 -41
  110. package/src/domains/subscription/infrastructure/hooks/README.md +0 -50
  111. package/src/domains/subscription/infrastructure/managers/README.md +0 -41
  112. package/src/domains/subscription/infrastructure/services/README.md +0 -42
  113. package/src/domains/subscription/infrastructure/utils/README.md +0 -41
  114. package/src/domains/subscription/presentation/components/README.md +0 -155
  115. package/src/domains/subscription/presentation/components/details/CreditRow.md +0 -92
  116. package/src/domains/subscription/presentation/components/details/DetailRow.md +0 -91
  117. package/src/domains/subscription/presentation/components/details/PremiumDetailsCard.md +0 -93
  118. package/src/domains/subscription/presentation/components/details/PremiumStatusBadge.md +0 -91
  119. package/src/domains/subscription/presentation/components/details/README.md +0 -99
  120. package/src/domains/subscription/presentation/components/feedback/PaywallFeedbackModal.md +0 -90
  121. package/src/domains/subscription/presentation/components/feedback/README.md +0 -99
  122. package/src/domains/subscription/presentation/components/paywall/PaywallModal.md +0 -94
  123. package/src/domains/subscription/presentation/components/paywall/README.md +0 -54
  124. package/src/domains/subscription/presentation/components/sections/README.md +0 -99
  125. package/src/domains/subscription/presentation/components/sections/SubscriptionSection.md +0 -94
  126. package/src/domains/subscription/presentation/utils/README.md +0 -31
  127. package/src/domains/wallet/README.md +0 -51
  128. package/src/domains/wallet/domain/README.md +0 -41
  129. package/src/domains/wallet/infrastructure/README.md +0 -41
  130. package/src/domains/wallet/presentation/components/README.md +0 -41
  131. package/src/domains/wallet/presentation/hooks/README.md +0 -41
  132. package/src/shared/application/ports/README.md +0 -48
@@ -1,19 +1,6 @@
1
- /**
2
- * Subscription Query Keys
3
- * TanStack Query keys and constants for subscription state
4
- */
5
-
6
- /** Query cache time constants */
7
-
8
-
9
- /**
10
- * Query keys for TanStack Query
11
- */
12
1
  export const SUBSCRIPTION_QUERY_KEYS = {
13
2
  packages: ["subscription", "packages"] as const,
14
3
  initialized: (userId: string) =>
15
4
  ["subscription", "initialized", userId] as const,
16
5
  customerInfo: ["subscription", "customerInfo"] as const,
17
6
  } as const;
18
-
19
-
@@ -1,10 +1,3 @@
1
- /**
2
- * Purchase Package Hook
3
- * TanStack mutation for purchasing subscription packages
4
- * Credits are initialized by CustomerInfoListener (not here to avoid duplicates)
5
- * Auth info automatically read from @umituz/react-native-auth
6
- */
7
-
8
1
  import { useMutation, useQueryClient } from "@umituz/react-native-design-system";
9
2
  import type { PurchasesPackage } from "react-native-purchases";
10
3
  import { useAlert } from "@umituz/react-native-design-system";
@@ -19,17 +12,11 @@ import { subscriptionStatusQueryKeys } from "../../presentation/useSubscriptionS
19
12
  import { creditsQueryKeys } from "../../../credits/presentation/creditsQueryKeys";
20
13
  import { getErrorMessage } from "../../../revenuecat/core/errors";
21
14
 
22
- /** Purchase mutation result - simplified for presentation layer */
23
15
  interface PurchaseMutationResult {
24
16
  success: boolean;
25
17
  productId: string;
26
18
  }
27
19
 
28
- /**
29
- * Purchase a subscription package
30
- * Credits are initialized by CustomerInfoListener when entitlement becomes active
31
- * Auth info automatically read from auth store
32
- */
33
20
  export const usePurchasePackage = () => {
34
21
  const userId = useAuthStore(selectUserId);
35
22
  const isAnonymous = useAuthStore(selectIsAnonymous);
@@ -51,13 +38,11 @@ export const usePurchasePackage = () => {
51
38
  console.log(`[Purchase] Initializing and purchasing. User: ${userId}`);
52
39
  }
53
40
 
54
- await SubscriptionManager.initialize(userId);
55
41
  const success = await SubscriptionManager.purchasePackage(pkg, userId);
56
42
 
57
43
  return { success, productId };
58
44
  },
59
45
  onSuccess: (result) => {
60
-
61
46
  if (result.success) {
62
47
  showSuccess("Purchase Successful", "Your subscription is now active!");
63
48
  queryClient.invalidateQueries({
@@ -76,11 +61,8 @@ export const usePurchasePackage = () => {
76
61
  }
77
62
  },
78
63
  onError: (error) => {
79
-
80
- // Use map-based lookup - O(1) complexity
81
64
  const errorInfo = getErrorMessage(error);
82
65
 
83
- // Don't show alert for user cancellation
84
66
  if (!errorInfo.shouldShowAlert) {
85
67
  return;
86
68
  }
@@ -1,15 +1,9 @@
1
- /**
2
- * Restore Purchase Hook
3
- * TanStack mutation for restoring previous purchases
4
- * Credits are initialized by CustomerInfoListener (not here to avoid duplicates)
5
- * Auth info automatically read from @umituz/react-native-auth
6
- */
7
-
8
1
  import { useMutation, useQueryClient } from "@umituz/react-native-design-system";
9
2
  import { useAlert } from "@umituz/react-native-design-system";
10
3
  import {
11
4
  useAuthStore,
12
5
  selectUserId,
6
+ selectIsAnonymous,
13
7
  } from "@umituz/react-native-auth";
14
8
  import { SubscriptionManager } from "../../infrastructure/managers/SubscriptionManager";
15
9
  import { SUBSCRIPTION_QUERY_KEYS } from "./subscriptionQueryKeys";
@@ -22,29 +16,23 @@ interface RestoreResult {
22
16
  productId: string | null;
23
17
  }
24
18
 
25
- /**
26
- * Restore previous purchases
27
- * Credits are initialized by CustomerInfoListener when entitlement becomes active
28
- * Auth info automatically read from auth store
29
- */
30
19
  export const useRestorePurchase = () => {
31
20
  const userId = useAuthStore(selectUserId);
21
+ const isAnonymous = useAuthStore(selectIsAnonymous);
32
22
  const queryClient = useQueryClient();
33
23
  const { showSuccess, showInfo, showError } = useAlert();
34
24
 
35
25
  return useMutation({
36
26
  mutationFn: async (): Promise<RestoreResult> => {
37
- if (!userId) {
27
+ if (!userId || isAnonymous) {
38
28
  throw new Error("User not authenticated");
39
29
  }
40
30
 
41
- await SubscriptionManager.initialize(userId);
42
31
  const result = await SubscriptionManager.restore(userId);
43
32
  return result;
44
33
  },
45
34
  onSuccess: (result) => {
46
35
  if (result.success) {
47
- // Invalidate queries to refresh data
48
36
  queryClient.invalidateQueries({
49
37
  queryKey: SUBSCRIPTION_QUERY_KEYS.packages,
50
38
  });
@@ -57,7 +45,6 @@ export const useRestorePurchase = () => {
57
45
  });
58
46
  }
59
47
 
60
- // Show user feedback
61
48
  if (result.productId) {
62
49
  showSuccess("Restore Successful", "Your subscription has been restored!");
63
50
  } else {
@@ -66,7 +53,6 @@ export const useRestorePurchase = () => {
66
53
  }
67
54
  },
68
55
  onError: (error) => {
69
- // Use map-based lookup - O(1) complexity
70
56
  const errorInfo = getErrorMessage(error);
71
57
  showError(errorInfo.title, errorInfo.message);
72
58
  },
@@ -1,9 +1,3 @@
1
- /**
2
- * useRevenueCatTrialEligibility Hook
3
- * Checks if user is eligible for introductory offers via RevenueCat
4
- * Uses Apple's native mechanism for trial eligibility
5
- */
6
-
7
1
  import { useState, useEffect, useCallback, useRef } from "react";
8
2
  import { getRevenueCatService } from "../services/RevenueCatService";
9
3
  import {
@@ -14,24 +8,14 @@ import {
14
8
  type TrialEligibilityMap,
15
9
  } from "../utils/trialEligibilityUtils";
16
10
 
17
-
18
11
  interface UseRevenueCatTrialEligibilityResult {
19
- /** Map of product IDs to their trial eligibility */
20
12
  eligibilityMap: TrialEligibilityMap;
21
- /** Whether eligibility check is in progress */
22
13
  isLoading: boolean;
23
- /** Whether any product has an eligible trial */
24
14
  hasEligibleTrial: boolean;
25
- /** Check eligibility for specific product IDs */
26
15
  checkEligibility: (productIds: string[]) => Promise<void>;
27
- /** Get eligibility for a specific product */
28
16
  getProductEligibility: (productId: string) => ProductTrialEligibility | null;
29
17
  }
30
18
 
31
- /**
32
- * Hook to check trial eligibility via RevenueCat
33
- * Uses Apple's introductory offer eligibility system
34
- */
35
19
  export function useRevenueCatTrialEligibility(): UseRevenueCatTrialEligibilityResult {
36
20
  const [eligibilityMap, setEligibilityMap] = useState<TrialEligibilityMap>({});
37
21
  const [isLoading, setIsLoading] = useState(false);
@@ -95,4 +79,3 @@ export function useRevenueCatTrialEligibility(): UseRevenueCatTrialEligibilityRe
95
79
  getProductEligibility,
96
80
  };
97
81
  }
98
-
@@ -1,13 +1,3 @@
1
- /**
2
- * Subscription Packages Hook
3
- * TanStack query for fetching available packages (offerings)
4
- * Auth info automatically read from @umituz/react-native-auth
5
- *
6
- * IMPORTANT: Packages (offerings) are NOT user-specific - they're the same
7
- * for all users. We only need RevenueCat to be initialized, not necessarily
8
- * for a specific user. User-specific checks belong in useSubscriptionStatus.
9
- */
10
-
11
1
  import { useQuery, useQueryClient } from "@umituz/react-native-design-system";
12
2
  import { useEffect, useRef, useSyncExternalStore } from "react";
13
3
  import {
@@ -20,27 +10,18 @@ import {
20
10
  SUBSCRIPTION_QUERY_KEYS,
21
11
  } from "./subscriptionQueryKeys";
22
12
 
23
- /**
24
- * Fetch available subscription packages
25
- * Works for both authenticated and anonymous users
26
- * Auth info automatically read from auth store
27
- */
28
13
  export const useSubscriptionPackages = () => {
29
14
  const userId = useAuthStore(selectUserId);
30
15
  const isConfigured = SubscriptionManager.isConfigured();
31
16
  const queryClient = useQueryClient();
32
17
  const prevUserIdRef = useRef(userId);
33
18
 
34
- // Reactive initialization state - triggers re-render when BackgroundInitializer completes
35
19
  const initState = useSyncExternalStore(
36
20
  initializationState.subscribe,
37
21
  initializationState.getSnapshot,
38
22
  initializationState.getSnapshot,
39
23
  );
40
24
 
41
- // Packages (offerings) are NOT user-specific - same for all users.
42
- // We only need RevenueCat to be initialized at all.
43
- // Use reactive state OR direct manager check for backwards compatibility.
44
25
  const isInitialized = initState.initialized || SubscriptionManager.isInitialized();
45
26
 
46
27
  const query = useQuery({
@@ -1,9 +1,3 @@
1
- /**
2
- * Subscription TanStack Query Hooks
3
- * Server state management for RevenueCat subscriptions
4
- * Generic hooks for 100+ apps
5
- */
6
-
7
1
  export { useSubscriptionPackages } from "./useSubscriptionPackages";
8
2
  export { usePurchasePackage } from "./usePurchasePackage";
9
3
  export { useRestorePurchase } from "./useRestorePurchase";
@@ -1,25 +1,14 @@
1
- /**
2
- * Subscription Manager Utilities
3
- * Validation and helper functions for SubscriptionManager
4
- */
5
-
6
1
  import type { SubscriptionManagerConfig } from "./SubscriptionManager.types";
7
2
 
8
3
  import type { IRevenueCatService } from "../../../../shared/application/ports/IRevenueCatService";
9
4
  import { SubscriptionInternalState } from "./SubscriptionInternalState";
10
5
 
11
- /**
12
- * Validate that manager is configured
13
- */
14
6
  export function ensureConfigured(config: SubscriptionManagerConfig | null): void {
15
7
  if (!config) {
16
8
  throw new Error("SubscriptionManager not configured");
17
9
  }
18
10
  }
19
11
 
20
- /**
21
- * Get current user ID or throw
22
- */
23
12
  export function getCurrentUserIdOrThrow(state: SubscriptionInternalState): string {
24
13
  const userId = state.initCache.getCurrentUserId();
25
14
  if (userId === null || userId === undefined) {
@@ -28,9 +17,6 @@ export function getCurrentUserIdOrThrow(state: SubscriptionInternalState): strin
28
17
  return userId;
29
18
  }
30
19
 
31
- /**
32
- * Get service instance or initialize
33
- */
34
20
  export function getOrCreateService(
35
21
  currentInstance: IRevenueCatService | null
36
22
  ): IRevenueCatService {
@@ -48,9 +34,6 @@ export function getOrCreateService(
48
34
  return serviceInstance;
49
35
  }
50
36
 
51
- /**
52
- * Validate service is available
53
- */
54
37
  export function ensureServiceAvailable(service: IRevenueCatService | null): void {
55
38
  if (!service) {
56
39
  throw new Error("Service instance not available");
@@ -1,17 +1,3 @@
1
- /**
2
- * Reactive Initialization State
3
- * Uses useSyncExternalStore pattern to make SubscriptionManager
4
- * initialization state reactive for React components.
5
- *
6
- * Problem: SubscriptionManager.isInitializedForUser() is a plain method call.
7
- * When BackgroundInitializer completes (500ms+ after auth), React components
8
- * don't re-render because there's no reactive state change.
9
- *
10
- * Solution: This module provides a subscribe/getSnapshot interface that
11
- * React's useSyncExternalStore can use to trigger re-renders when
12
- * initialization completes.
13
- */
14
-
15
1
  type Listener = () => void;
16
2
 
17
3
  interface InitState {
@@ -34,27 +20,16 @@ export const initializationState = {
34
20
 
35
21
  getSnapshot: (): InitState => state,
36
22
 
37
- /**
38
- * Called by SubscriptionManager after successful initialization.
39
- * Triggers re-render in all subscribed React components.
40
- */
41
23
  markInitialized: (userId: string | null): void => {
42
24
  state = { initialized: true, userId };
43
25
  notifyListeners();
44
26
  },
45
27
 
46
- /**
47
- * Called when initialization starts for a new user (e.g., user switch).
48
- * Resets the state so queries know they need to wait.
49
- */
50
28
  markPending: (): void => {
51
29
  state = { initialized: false, userId: null };
52
30
  notifyListeners();
53
31
  },
54
32
 
55
- /**
56
- * Check if initialized for a specific user.
57
- */
58
33
  isInitializedForUser: (userId: string | null): boolean => {
59
34
  return state.initialized && state.userId === userId;
60
35
  },
@@ -1,31 +1,16 @@
1
- /**
2
- * Initialization Cache
3
- * Manages promise caching and user state for initialization
4
- * Thread-safe: Uses atomic promise-based locking pattern
5
- */
6
-
7
1
  export class InitializationCache {
8
2
  private initPromise: Promise<boolean> | null = null;
9
3
  private currentUserId: string | null = null;
10
- // Track which userId the promise is for
11
4
  private promiseUserId: string | null = null;
12
- // Track promise completion state
13
5
  private promiseCompleted = true;
14
- // Pending initialization queue
15
6
  private pendingQueue: Map<string, Promise<boolean>> = new Map();
16
7
 
17
- /**
18
- * Atomically check if reinitialization is needed AND reserve the slot
19
- * Returns: { shouldInit: boolean, existingPromise: Promise | null }
20
- */
21
8
  tryAcquireInitialization(userId: string): { shouldInit: boolean; existingPromise: Promise<boolean> | null } {
22
- // Check if there's already a pending promise for this user in the queue
23
9
  const queuedPromise = this.pendingQueue.get(userId);
24
10
  if (queuedPromise) {
25
11
  return { shouldInit: false, existingPromise: queuedPromise };
26
12
  }
27
13
 
28
- // If already initialized for this user and promise completed successfully
29
14
  if (
30
15
  this.initPromise &&
31
16
  this.currentUserId === userId &&
@@ -35,7 +20,6 @@ export class InitializationCache {
35
20
  return { shouldInit: false, existingPromise: this.initPromise };
36
21
  }
37
22
 
38
- // Different user or not initialized - need to initialize
39
23
  return { shouldInit: true, existingPromise: null };
40
24
  }
41
25
 
@@ -45,9 +29,6 @@ export class InitializationCache {
45
29
 
46
30
  const targetUserId = userId;
47
31
 
48
- // Build the handled chain that ALWAYS resolves (never rejects).
49
- // This is critical: pendingQueue must store a non-rejectable promise so that
50
- // callers who receive it via tryAcquireInitialization never get an unhandled rejection.
51
32
  const chain: Promise<boolean> = promise
52
33
  .then((result) => {
53
34
  if (result && this.promiseUserId === targetUserId) {
@@ -67,11 +48,9 @@ export class InitializationCache {
67
48
  return false as boolean;
68
49
  })
69
50
  .finally(() => {
70
- // Remove from queue when complete
71
51
  this.pendingQueue.delete(targetUserId);
72
52
  });
73
53
 
74
- // Store the chain (not the original promise) so callers never receive a rejection
75
54
  this.initPromise = chain;
76
55
  this.pendingQueue.set(userId, chain);
77
56
  }
@@ -1,8 +1,3 @@
1
- /**
2
- * Premium Status Syncer
3
- * Syncs premium status to database via callbacks
4
- */
5
-
6
1
  import type { CustomerInfo } from "react-native-purchases";
7
2
  import type { RevenueCatConfig, PackageType } from "../../../revenuecat/core/types";
8
3
  import type { PurchaseSource } from "../../../subscription/core/SubscriptionConstants";
@@ -85,7 +80,7 @@ export async function notifyRestoreCompleted(
85
80
 
86
81
  try {
87
82
  await config.onRestoreCompleted(userId, isPremium, customerInfo);
88
- } catch (_error) {
89
- // Silently fail callback notifications to prevent crashing the main flow
83
+ } catch (error) {
84
+ console.error('[PremiumStatusSyncer] Restore callback failed:', error instanceof Error ? error.message : String(error));
90
85
  }
91
86
  }
@@ -1,8 +1,3 @@
1
- /**
2
- * Auth Purchase State Manager
3
- * Manages global state for auth-aware purchase operations
4
- */
5
-
6
1
  import type { PurchasesPackage } from "react-native-purchases";
7
2
  import type { PurchaseSource } from "../../core/SubscriptionConstants";
8
3
 
@@ -15,9 +10,12 @@ interface SavedPurchaseState {
15
10
  pkg: PurchasesPackage;
16
11
  source: PurchaseSource;
17
12
  timestamp: number;
13
+ sessionId: string;
18
14
  }
19
15
 
20
- const SAVED_PURCHASE_EXPIRY_MS = 5 * 60 * 1000;
16
+ const SAVED_PURCHASE_EXPIRY_MS = 2 * 60 * 1000;
17
+
18
+ let currentSessionId = "";
21
19
 
22
20
  class AuthPurchaseStateManager {
23
21
  private authProvider: PurchaseAuthProvider | null = null;
@@ -31,11 +29,16 @@ class AuthPurchaseStateManager {
31
29
  return this.authProvider;
32
30
  }
33
31
 
32
+ setSessionId(sessionId: string): void {
33
+ currentSessionId = sessionId;
34
+ }
35
+
34
36
  savePurchase(pkg: PurchasesPackage, source: PurchaseSource): void {
35
37
  this.savedPurchaseState = {
36
38
  pkg,
37
39
  source,
38
40
  timestamp: Date.now(),
41
+ sessionId: currentSessionId,
39
42
  };
40
43
  }
41
44
 
@@ -44,6 +47,11 @@ class AuthPurchaseStateManager {
44
47
  return null;
45
48
  }
46
49
 
50
+ if (this.savedPurchaseState.sessionId !== currentSessionId) {
51
+ this.savedPurchaseState = null;
52
+ return null;
53
+ }
54
+
47
55
  const isExpired = Date.now() - this.savedPurchaseState.timestamp > SAVED_PURCHASE_EXPIRY_MS;
48
56
  if (isExpired) {
49
57
  this.savedPurchaseState = null;
@@ -60,9 +68,14 @@ class AuthPurchaseStateManager {
60
68
  this.savedPurchaseState = null;
61
69
  }
62
70
 
71
+ onUserChanged(): void {
72
+ this.savedPurchaseState = null;
73
+ }
74
+
63
75
  cleanup(): void {
64
76
  this.authProvider = null;
65
77
  this.savedPurchaseState = null;
78
+ currentSessionId = "";
66
79
  }
67
80
  }
68
81
 
@@ -4,6 +4,7 @@ const PACKAGE_TIER_ORDER: Record<string, number> = {
4
4
  weekly: 1,
5
5
  monthly: 2,
6
6
  yearly: 3,
7
+ lifetime: 4,
7
8
  unknown: 0,
8
9
  };
9
10
 
@@ -1,29 +1,18 @@
1
- /**
2
- * Trial Eligibility Utilities
3
- * Business logic for checking trial eligibility
4
- */
5
-
6
1
  import Purchases, {
7
2
  type IntroEligibility,
8
3
  INTRO_ELIGIBILITY_STATUS,
9
4
  } from "react-native-purchases";
10
5
 
11
- /** Trial eligibility info for a single product */
12
6
  export interface ProductTrialEligibility {
13
7
  productId: string;
14
8
  eligible: boolean;
15
9
  trialDurationDays?: number;
16
10
  }
17
11
 
18
- /** Map of product ID to eligibility */
19
12
  export type TrialEligibilityMap = Record<string, ProductTrialEligibility>;
20
13
 
21
- /** Default trial duration in days */
22
14
  const DEFAULT_TRIAL_DURATION_DAYS = 7;
23
15
 
24
- /**
25
- * Check trial eligibility for product IDs
26
- */
27
16
  export async function checkTrialEligibility(
28
17
  productIds: string[]
29
18
  ): Promise<TrialEligibilityMap> {
@@ -47,10 +36,6 @@ export async function checkTrialEligibility(
47
36
  return result;
48
37
  }
49
38
 
50
- /**
51
- * Create fallback eligibility map (all eligible)
52
- * Used when eligibility check fails
53
- */
54
39
  export function createFallbackEligibilityMap(
55
40
  productIds: string[]
56
41
  ): TrialEligibilityMap {
@@ -67,9 +52,6 @@ export function createFallbackEligibilityMap(
67
52
  return result;
68
53
  }
69
54
 
70
- /**
71
- * Check if any product has eligible trial
72
- */
73
55
  export function hasAnyEligibleTrial(
74
56
  eligibilityMap: TrialEligibilityMap
75
57
  ): boolean {
@@ -1,8 +1,3 @@
1
- /**
2
- * Premium Details Card Styles
3
- * StyleSheet for PremiumDetailsCard component
4
- */
5
-
6
1
  import { StyleSheet } from "react-native";
7
2
 
8
3
  export const styles = StyleSheet.create({
@@ -1,8 +1,3 @@
1
- /**
2
- * Premium Details Card Types
3
- * Type definitions for premium subscription details display
4
- */
5
-
6
1
  import type { SubscriptionStatusType } from "./PremiumStatusBadge";
7
2
 
8
3
  export interface CreditInfo {
@@ -1,8 +1,3 @@
1
- /**
2
- * Paywall Feedback Styles
3
- * Generates styles based on design tokens
4
- */
5
-
6
1
  import { StyleSheet } from "react-native";
7
2
  import type { DesignTokens } from "@umituz/react-native-design-system";
8
3
 
@@ -1,7 +1,3 @@
1
- /**
2
- * Presentation Layer - Stores
3
- */
4
-
5
1
  export {
6
2
  usePurchaseLoadingStore,
7
3
  selectIsPurchasing,
@@ -1,25 +1,13 @@
1
- /**
2
- * Purchase Loading Store
3
- * Global state for tracking purchase loading across the app
4
- * Supports concurrent purchases via Map-based tracking
5
- * Used by both PaywallModal and useSavedPurchaseAutoExecution
6
- */
7
-
8
1
  import { create } from "zustand";
9
2
 
10
3
  interface PurchaseLoadingState {
11
- /** Map of product IDs to purchase sources (supports concurrent purchases) */
12
4
  activePurchases: Map<string, "manual" | "auto-execution">;
13
5
  }
14
6
 
15
7
  interface PurchaseLoadingActions {
16
- /** Start purchase loading state for a product */
17
8
  startPurchase: (productId: string, source: "manual" | "auto-execution") => void;
18
- /** End purchase loading state for a product */
19
9
  endPurchase: (productId: string) => void;
20
- /** Check if any purchase is in progress, or if a specific product is being purchased */
21
10
  isPurchasing: (productId?: string) => boolean;
22
- /** Reset all state */
23
11
  reset: () => void;
24
12
  }
25
13
 
@@ -59,5 +47,4 @@ export const usePurchaseLoadingStore = create<PurchaseLoadingStore>((set, get) =
59
47
  },
60
48
  }));
61
49
 
62
- // Selectors for optimized re-renders
63
50
  export const selectIsPurchasing = (state: PurchaseLoadingStore) => state.activePurchases.size > 0;
@@ -1,9 +1,4 @@
1
- /**
2
- * Auth-Aware Purchase Hook
3
- * Handles purchase flow with authentication requirement
4
- */
5
-
6
- import { useCallback } from "react";
1
+ import { useCallback, useEffect, useRef } from "react";
7
2
  import type { PurchasesPackage } from "react-native-purchases";
8
3
  import { usePremium } from "./usePremium";
9
4
  import type { PurchaseSource } from "../core/SubscriptionConstants";
@@ -35,10 +30,43 @@ export const useAuthAwarePurchase = (
35
30
  params?: UseAuthAwarePurchaseParams
36
31
  ): UseAuthAwarePurchaseResult => {
37
32
  const { purchasePackage, restorePurchase } = usePremium();
33
+ const isExecutingSavedRef = useRef(false);
34
+
35
+ const executeSavedPurchase = useCallback(async (): Promise<boolean> => {
36
+ const saved = authPurchaseStateManager.getSavedPurchase();
37
+ if (!saved) {
38
+ return false;
39
+ }
40
+
41
+ try {
42
+ const result = await purchasePackage(saved.pkg);
43
+ if (result) {
44
+ authPurchaseStateManager.clearSavedPurchase();
45
+ }
46
+ return result;
47
+ } catch {
48
+ authPurchaseStateManager.clearSavedPurchase();
49
+ return false;
50
+ }
51
+ }, [purchasePackage]);
52
+
53
+ useEffect(() => {
54
+ const authProvider = authPurchaseStateManager.getProvider();
55
+ if (!authProvider) return;
56
+
57
+ const isAuth = authProvider.isAuthenticated();
58
+ const hasSavedPurchase = !!authPurchaseStateManager.getSavedPurchase();
59
+
60
+ if (isAuth && hasSavedPurchase && !isExecutingSavedRef.current) {
61
+ isExecutingSavedRef.current = true;
62
+ executeSavedPurchase().finally(() => {
63
+ isExecutingSavedRef.current = false;
64
+ });
65
+ }
66
+ }, [executeSavedPurchase]);
38
67
 
39
68
  const handlePurchase = useCallback(
40
69
  async (pkg: PurchasesPackage, source?: PurchaseSource): Promise<boolean> => {
41
-
42
70
  const authProvider = authPurchaseStateManager.getProvider();
43
71
 
44
72
  if (!authProvider) {
@@ -61,7 +89,6 @@ export const useAuthAwarePurchase = (
61
89
  );
62
90
 
63
91
  const handleRestore = useCallback(async (): Promise<boolean> => {
64
-
65
92
  const authProvider = authPurchaseStateManager.getProvider();
66
93
 
67
94
  if (!authProvider) {
@@ -78,19 +105,6 @@ export const useAuthAwarePurchase = (
78
105
  return result;
79
106
  }, [restorePurchase]);
80
107
 
81
- const executeSavedPurchase = useCallback(async (): Promise<boolean> => {
82
- const saved = authPurchaseStateManager.getSavedPurchase();
83
- if (!saved) {
84
- return false;
85
- }
86
-
87
- const result = await purchasePackage(saved.pkg);
88
- if (result) {
89
- authPurchaseStateManager.clearSavedPurchase();
90
- }
91
- return result;
92
- }, [purchasePackage]);
93
-
94
108
  return {
95
109
  handlePurchase,
96
110
  handleRestore,