@umituz/react-native-subscription 2.27.96 → 2.27.98

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 (44) hide show
  1. package/package.json +1 -1
  2. package/src/domains/credits/core/Credits.ts +3 -11
  3. package/src/domains/credits/presentation/useDeductCredit.ts +0 -4
  4. package/src/domains/paywall/components/PaywallContainer.tsx +17 -1
  5. package/src/domains/paywall/components/PaywallContainer.types.ts +2 -1
  6. package/src/domains/paywall/components/PaywallModal.tsx +0 -6
  7. package/src/domains/paywall/hooks/usePaywallActions.ts +1 -1
  8. package/src/domains/subscription/application/SubscriptionInitializer.ts +0 -6
  9. package/src/domains/subscription/infrastructure/handlers/PackageHandler.ts +23 -12
  10. package/src/domains/subscription/infrastructure/hooks/subscriptionQueryKeys.ts +4 -4
  11. package/src/domains/subscription/infrastructure/hooks/useCustomerInfo.ts +0 -19
  12. package/src/domains/subscription/infrastructure/hooks/useInitializeSubscription.ts +0 -16
  13. package/src/domains/subscription/infrastructure/hooks/usePurchasePackage.ts +0 -31
  14. package/src/domains/subscription/infrastructure/hooks/useRestorePurchase.ts +0 -27
  15. package/src/domains/subscription/infrastructure/hooks/useRevenueCatTrialEligibility.ts +0 -12
  16. package/src/domains/subscription/infrastructure/hooks/useSubscriptionPackages.ts +1 -7
  17. package/src/domains/subscription/infrastructure/hooks/useSubscriptionQueries.ts +0 -2
  18. package/src/domains/subscription/infrastructure/services/CustomerInfoListenerManager.ts +0 -21
  19. package/src/domains/subscription/infrastructure/services/RevenueCatInitializer.ts +0 -9
  20. package/src/domains/subscription/infrastructure/utils/InitializationCache.ts +20 -7
  21. package/src/domains/subscription/infrastructure/utils/PremiumStatusSyncer.ts +1 -16
  22. package/src/domains/subscription/presentation/screens/SubscriptionDetailScreen.tsx +49 -24
  23. package/src/domains/subscription/presentation/screens/components/CreditsList.tsx +14 -1
  24. package/src/domains/subscription/presentation/screens/components/SubscriptionActions.tsx +6 -1
  25. package/src/domains/subscription/presentation/screens/components/SubscriptionHeader.tsx +20 -1
  26. package/src/domains/subscription/presentation/screens/components/UpgradePrompt.tsx +13 -1
  27. package/src/domains/subscription/presentation/stores/purchaseLoadingStore.ts +0 -3
  28. package/src/domains/subscription/presentation/useAuthAwarePurchase.ts +1 -1
  29. package/src/domains/subscription/presentation/useFeatureGate.ts +9 -55
  30. package/src/domains/subscription/presentation/usePaywallVisibility.ts +1 -1
  31. package/src/domains/subscription/presentation/useSubscriptionStatus.ts +1 -5
  32. package/src/init/index.ts +0 -3
  33. package/src/presentation/hooks/index.ts +0 -4
  34. package/src/shared/infrastructure/SubscriptionEventBus.ts +27 -0
  35. package/src/utils/packageTypeDetector.ts +0 -4
  36. package/src/domains/subscription/presentation/screens/components/DevTestSection.tsx +0 -125
  37. package/src/domains/subscription/presentation/types/README.md +0 -22
  38. package/src/domains/subscription/presentation/types/SubscriptionDetailTypes.ts +0 -153
  39. package/src/domains/subscription/presentation/types/SubscriptionSettingsTypes.ts +0 -74
  40. package/src/domains/subscription/presentation/useAuthSubscriptionSync.ts +0 -63
  41. package/src/domains/subscription/presentation/usePremiumGate.ts +0 -84
  42. package/src/domains/subscription/presentation/useSavedPurchaseAutoExecution.ts +0 -148
  43. package/src/domains/subscription/presentation/useSubscriptionSettingsConfig.ts +0 -115
  44. package/src/domains/subscription/presentation/useSubscriptionSettingsConfig.utils.ts +0 -57
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-subscription",
3
- "version": "2.27.96",
3
+ "version": "2.27.98",
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",
@@ -6,6 +6,7 @@
6
6
  */
7
7
 
8
8
  import type { SubscriptionPackageType } from "../../../utils/packageTypeDetector";
9
+ // Types imported from SubscriptionConstants are used directly in UserCredits interface
9
10
  import type {
10
11
  SubscriptionStatusType,
11
12
  PeriodType,
@@ -15,15 +16,6 @@ import type {
15
16
  PurchaseType
16
17
  } from "../../subscription/core/SubscriptionConstants";
17
18
 
18
- export type {
19
- SubscriptionStatusType,
20
- PeriodType,
21
- PackageType,
22
- Platform,
23
- PurchaseSource,
24
- PurchaseType
25
- };
26
-
27
19
  export type CreditType = "text" | "image";
28
20
 
29
21
  /** Single Source of Truth for user subscription + credits data */
@@ -67,10 +59,10 @@ export interface CreditAllocation {
67
59
  credits: number;
68
60
  }
69
61
 
70
- export type PackageAllocationMap = Record<
62
+ export type PackageAllocationMap = Partial<Record<
71
63
  Exclude<SubscriptionPackageType, "unknown">,
72
64
  CreditAllocation
73
- >;
65
+ >>;
74
66
 
75
67
  export interface CreditsConfig {
76
68
  collectionName: string;
@@ -82,24 +82,20 @@ export const useDeductCredit = ({
82
82
 
83
83
  const deductCredit = useCallback(async (cost: number = 1): Promise<boolean> => {
84
84
  if (typeof __DEV__ !== "undefined" && __DEV__) {
85
- console.log("[useDeductCredit] Attempting to deduct:", cost);
86
85
  }
87
86
  try {
88
87
  const res = await mutation.mutateAsync(cost);
89
88
  if (!res.success) {
90
89
  if (typeof __DEV__ !== "undefined" && __DEV__) {
91
- console.log("[useDeductCredit] Deduction failed:", res.error?.code, res.error?.message);
92
90
  }
93
91
  if (res.error?.code === "CREDITS_EXHAUSTED") onCreditsExhausted?.();
94
92
  return false;
95
93
  }
96
94
  if (typeof __DEV__ !== "undefined" && __DEV__) {
97
- console.log("[useDeductCredit] Deduction successful, remaining:", res.remainingCredits);
98
95
  }
99
96
  return true;
100
97
  } catch (err) {
101
98
  if (typeof __DEV__ !== "undefined" && __DEV__) {
102
- console.log("[useDeductCredit] Deduction error:", err);
103
99
  }
104
100
  return false;
105
101
  }
@@ -49,9 +49,25 @@ export const PaywallContainer: React.FC<PaywallContainerProps> = (props) => {
49
49
  });
50
50
 
51
51
  // Check trial eligibility only if trialConfig is enabled
52
+ // Use ref to track if we've already checked for these packages to avoid redundant calls
53
+ const checkedPackagesRef = React.useRef<string[]>([]);
54
+
52
55
  useEffect(() => {
53
56
  if (!trialConfig?.enabled) return;
54
57
  if (packages.length === 0) return;
58
+ if (isLoading) return; // Wait for packages to fully load
59
+
60
+ // Get current package identifiers
61
+ const currentPackageIds = packages.map((pkg) => pkg.product.identifier);
62
+ const sortedIds = [...currentPackageIds].sort().join(",");
63
+
64
+ // Skip if we've already checked these exact packages
65
+ if (checkedPackagesRef.current.join(",") === sortedIds) {
66
+ return;
67
+ }
68
+
69
+ // Update ref
70
+ checkedPackagesRef.current = currentPackageIds;
55
71
 
56
72
  // Get all actual product IDs from packages
57
73
  const allProductIds = packages.map((pkg) => pkg.product.identifier);
@@ -72,7 +88,7 @@ export const PaywallContainer: React.FC<PaywallContainerProps> = (props) => {
72
88
  if (productIdsToCheck.length > 0) {
73
89
  checkEligibility(productIdsToCheck);
74
90
  }
75
- }, [packages, checkEligibility, trialConfig?.enabled, trialConfig?.eligibleProductIds]);
91
+ }, [packages, isLoading, checkEligibility, trialConfig?.enabled, trialConfig?.eligibleProductIds]);
76
92
 
77
93
  // Convert eligibility map to format expected by PaywallModal
78
94
  // Only process if trial is enabled
@@ -5,7 +5,8 @@
5
5
 
6
6
  import type { ImageSourcePropType } from "react-native";
7
7
  import type { PaywallTranslations, PaywallLegalUrls, SubscriptionFeature } from "../entities/types";
8
- import type { PurchaseSource, PackageAllocationMap } from "../../credits/core/Credits";
8
+ import type { PurchaseSource } from "../../subscription/core/SubscriptionConstants";
9
+ import type { PackageAllocationMap } from "../../credits/core/Credits";
9
10
 
10
11
  /**
11
12
  * Trial display configuration
@@ -74,7 +74,6 @@ export const PaywallModal: React.FC<PaywallModalProps> = React.memo((props) => {
74
74
  if (!selectedPlanId || !onPurchase) return;
75
75
 
76
76
  if (__DEV__) {
77
- console.log("[PaywallModal] handlePurchase starting:", { selectedPlanId });
78
77
  }
79
78
 
80
79
  setIsLocalProcessing(true);
@@ -84,11 +83,9 @@ export const PaywallModal: React.FC<PaywallModalProps> = React.memo((props) => {
84
83
  const pkg = packages.find((p) => p.product.identifier === selectedPlanId);
85
84
  if (pkg) {
86
85
  if (__DEV__) {
87
- console.log("[PaywallModal] Calling onPurchase:", { productId: pkg.product.identifier });
88
86
  }
89
87
  await onPurchase(pkg);
90
88
  if (__DEV__) {
91
- console.log("[PaywallModal] onPurchase completed");
92
89
  }
93
90
  }
94
91
  } catch (error) {
@@ -100,7 +97,6 @@ export const PaywallModal: React.FC<PaywallModalProps> = React.memo((props) => {
100
97
  setIsLocalProcessing(false);
101
98
  endPurchase();
102
99
  if (__DEV__) {
103
- console.log("[PaywallModal] handlePurchase finished");
104
100
  }
105
101
  }
106
102
  }, [selectedPlanId, packages, onPurchase, startPurchase, endPurchase]);
@@ -109,14 +105,12 @@ export const PaywallModal: React.FC<PaywallModalProps> = React.memo((props) => {
109
105
  if (!onRestore || isProcessing) return;
110
106
 
111
107
  if (__DEV__) {
112
- console.log("[PaywallModal] handleRestore starting");
113
108
  }
114
109
 
115
110
  setIsLocalProcessing(true);
116
111
  try {
117
112
  await onRestore();
118
113
  if (__DEV__) {
119
- console.log("[PaywallModal] handleRestore completed");
120
114
  }
121
115
  } finally {
122
116
  setIsLocalProcessing(false);
@@ -2,7 +2,7 @@ import { useCallback } from "react";
2
2
  import type { PurchasesPackage } from "react-native-purchases";
3
3
  import { useRestorePurchase } from "../../subscription/infrastructure/hooks/useRestorePurchase";
4
4
  import { useAuthAwarePurchase } from "../../subscription/presentation/useAuthAwarePurchase";
5
- import type { PurchaseSource } from "../../credits/core/Credits";
5
+ import type { PurchaseSource } from "../../subscription/core/SubscriptionConstants";
6
6
 
7
7
  interface UsePaywallActionsProps {
8
8
  source?: PurchaseSource;
@@ -94,9 +94,6 @@ export const initializeSubscription = async (config: SubscriptionInitConfig): Pr
94
94
 
95
95
  const initializeInBackground = async (userId?: string): Promise<void> => {
96
96
  await SubscriptionManager.initialize(userId);
97
- if (__DEV__) {
98
- console.log('[SubscriptionInitializer] Background init complete');
99
- }
100
97
  };
101
98
 
102
99
  // 5. Start Background Init
@@ -110,9 +107,6 @@ export const initializeSubscription = async (config: SubscriptionInitConfig): Pr
110
107
 
111
108
  // 6. Listen for Auth Changes
112
109
  setupAuthStateListener(() => auth, (newUserId) => {
113
- if (__DEV__) {
114
- console.log('[SubscriptionInitializer] Auth changed, re-init:', newUserId);
115
- }
116
110
  initializeInBackground(newUserId);
117
111
  });
118
112
  };
@@ -25,21 +25,32 @@ export class PackageHandler {
25
25
 
26
26
  async fetchPackages(): Promise<PurchasesPackage[]> {
27
27
  if (!this.service.isInitialized()) {
28
- throw new Error("Service not initialized");
28
+ throw new Error("Service not initialized. Please initialize before fetching packages.");
29
29
  }
30
30
 
31
- const offering = await this.service.fetchOfferings();
32
-
33
- if (!offering) {
34
- throw new Error("No offerings available");
31
+ try {
32
+ const offering = await this.service.fetchOfferings();
33
+
34
+ if (!offering) {
35
+ // Return empty array instead of throwing - allows graceful degradation
36
+ return [];
37
+ }
38
+
39
+ const packages = offering.availablePackages;
40
+ if (!packages || packages.length === 0) {
41
+ // Return empty array instead of throwing - allows graceful degradation
42
+ return [];
43
+ }
44
+
45
+ return packages;
46
+ } catch (error) {
47
+ // Re-throw with more context
48
+ throw new Error(
49
+ `Failed to fetch subscription packages. ${
50
+ error instanceof Error ? error.message : "Unknown error"
51
+ }`
52
+ );
35
53
  }
36
-
37
- const packages = offering.availablePackages;
38
- if (!packages) {
39
- throw new Error("No packages available in offering");
40
- }
41
-
42
- return packages;
43
54
  }
44
55
 
45
56
  async purchase(pkg: PurchasesPackage, userId: string): Promise<boolean> {
@@ -3,6 +3,9 @@
3
3
  * TanStack Query keys and constants for subscription state
4
4
  */
5
5
 
6
+ /** Query cache time constants */
7
+
8
+
6
9
  /**
7
10
  * Query keys for TanStack Query
8
11
  */
@@ -12,7 +15,4 @@ export const SUBSCRIPTION_QUERY_KEYS = {
12
15
  ["subscription", "initialized", userId] as const,
13
16
  } as const;
14
17
 
15
- // No cache - always fetch fresh data for subscription packages
16
- // This ensures users always see real-time subscription status
17
- export const STALE_TIME = 0; // Always stale - refetch immediately
18
- export const GC_TIME = 0; // Don't cache - garbage collect immediately
18
+
@@ -71,23 +71,10 @@ export function useCustomerInfo(): UseCustomerInfoResult {
71
71
  const info = await Purchases.getCustomerInfo();
72
72
 
73
73
  setCustomerInfo(info);
74
-
75
- if (__DEV__) {
76
- console.log('[DEBUG useCustomerInfo] Fetched:', {
77
- hasActiveEntitlements: Object.keys(info.entitlements.active).length > 0,
78
- latestExpiration: info.latestExpirationDate || "none",
79
- });
80
- }
81
74
  } catch (err) {
82
75
  const errorMessage =
83
76
  err instanceof Error ? err.message : "Failed to fetch customer info";
84
77
  setError(errorMessage);
85
-
86
- if (__DEV__) {
87
- console.error('[DEBUG useCustomerInfo] Error:', {
88
- error: err,
89
- });
90
- }
91
78
  } finally {
92
79
  setLoading(false);
93
80
  setIsFetching(false);
@@ -100,12 +87,6 @@ export function useCustomerInfo(): UseCustomerInfoResult {
100
87
 
101
88
  // Listen for real-time updates (renewals, purchases, restore)
102
89
  const listener = (info: CustomerInfo) => {
103
- if (__DEV__) {
104
- console.log('[DEBUG useCustomerInfo] Listener update:', {
105
- hasActiveEntitlements: Object.keys(info.entitlements.active).length > 0,
106
- });
107
- }
108
-
109
90
  setCustomerInfo(info);
110
91
  setError(null);
111
92
  };
@@ -19,10 +19,6 @@ export const useInitializeSubscription = (userId: string | undefined) => {
19
19
  throw new Error("User not authenticated");
20
20
  }
21
21
 
22
- if (__DEV__) {
23
- console.log('[DEBUG useInitializeSubscription] Initializing:', { userId });
24
- }
25
-
26
22
  return SubscriptionManager.initialize(userId);
27
23
  },
28
24
  onSuccess: () => {
@@ -30,18 +26,6 @@ export const useInitializeSubscription = (userId: string | undefined) => {
30
26
  queryClient.invalidateQueries({
31
27
  queryKey: SUBSCRIPTION_QUERY_KEYS.packages,
32
28
  });
33
-
34
- if (__DEV__) {
35
- console.log('[DEBUG useInitializeSubscription] Success:', { userId });
36
- }
37
- }
38
- },
39
- onError: (error) => {
40
- if (__DEV__) {
41
- console.error('[DEBUG useInitializeSubscription] Error:', {
42
- error,
43
- userId: userId ?? "ANONYMOUS",
44
- });
45
29
  }
46
30
  },
47
31
  });
@@ -40,33 +40,12 @@ export const usePurchasePackage = () => {
40
40
  }
41
41
 
42
42
  const productId = pkg.product.identifier;
43
-
44
- if (__DEV__) {
45
- console.log('[DEBUG usePurchasePackage] Starting purchase:', {
46
- packageId: pkg.identifier,
47
- productId,
48
- userId,
49
- });
50
- }
51
-
52
43
  const success = await SubscriptionManager.purchasePackage(pkg);
53
44
 
54
- if (__DEV__) {
55
- console.log('[DEBUG usePurchasePackage] Purchase result:', {
56
- success,
57
- packageId: pkg.identifier,
58
- productId,
59
- userId,
60
- });
61
- }
62
-
63
45
  return { success, productId };
64
46
  },
65
47
  onSuccess: (result) => {
66
48
  if (result.success) {
67
- if (__DEV__) {
68
- console.log('[DEBUG usePurchasePackage] onSuccess - invalidating queries');
69
- }
70
49
  showSuccess("Purchase Successful", "Your subscription is now active!");
71
50
  queryClient.invalidateQueries({
72
51
  queryKey: SUBSCRIPTION_QUERY_KEYS.packages,
@@ -77,20 +56,10 @@ export const usePurchasePackage = () => {
77
56
  });
78
57
  }
79
58
  } else {
80
- if (__DEV__) {
81
- console.log('[DEBUG usePurchasePackage] onSuccess but result.success=false');
82
- }
83
59
  showError("Purchase Failed", "Unable to complete purchase. Please try again.");
84
60
  }
85
61
  },
86
62
  onError: (error) => {
87
- if (__DEV__) {
88
- console.error('[DEBUG usePurchasePackage] onError:', {
89
- error,
90
- userId: userId ?? "ANONYMOUS",
91
- });
92
- }
93
-
94
63
  let title = "Purchase Error";
95
64
  let message = "Unable to complete purchase. Please try again.";
96
65
 
@@ -34,26 +34,7 @@ export const useRestorePurchase = () => {
34
34
  throw new Error("User not authenticated");
35
35
  }
36
36
 
37
- if (__DEV__) {
38
- console.log('[DEBUG useRestorePurchase] Starting restore:', { userId });
39
- }
40
-
41
37
  const result = await SubscriptionManager.restore();
42
-
43
- if (result.success) {
44
- if (__DEV__) {
45
- console.log('[DEBUG useRestorePurchase] Restore successful:', {
46
- userId,
47
- productId: result.productId,
48
- });
49
- }
50
- // Credits will be initialized by CustomerInfoListener
51
- } else {
52
- if (__DEV__) {
53
- console.log('[DEBUG useRestorePurchase] Restore failed:', { userId });
54
- }
55
- }
56
-
57
38
  return result;
58
39
  },
59
40
  onSuccess: (result) => {
@@ -68,13 +49,5 @@ export const useRestorePurchase = () => {
68
49
  }
69
50
  }
70
51
  },
71
- onError: (error) => {
72
- if (__DEV__) {
73
- console.error('[DEBUG useRestorePurchase] Restore mutation failed:', {
74
- error,
75
- userId: userId ?? "ANONYMOUS",
76
- });
77
- }
78
- },
79
52
  });
80
53
  };
@@ -85,9 +85,6 @@ export function useRevenueCatTrialEligibility(): UseRevenueCatTrialEligibilityRe
85
85
 
86
86
  const service = getRevenueCatService();
87
87
  if (!service || !service.isInitialized()) {
88
- if (__DEV__) {
89
- console.log("[TrialEligibility] RevenueCat not initialized");
90
- }
91
88
  return;
92
89
  }
93
90
 
@@ -109,12 +106,6 @@ export function useRevenueCatTrialEligibility(): UseRevenueCatTrialEligibilityRe
109
106
  eligible: isEligible,
110
107
  trialDurationDays: 7, // Default to 7 days as configured in App Store Connect
111
108
  };
112
-
113
- if (__DEV__) {
114
- console.log(
115
- `[TrialEligibility] ${productId}: ${isEligible ? "ELIGIBLE" : "NOT_ELIGIBLE"}`
116
- );
117
- }
118
109
  }
119
110
 
120
111
  // Update cache
@@ -127,9 +118,6 @@ export function useRevenueCatTrialEligibility(): UseRevenueCatTrialEligibilityRe
127
118
  setEligibilityMap((prev) => ({ ...prev, ...newMap }));
128
119
  }
129
120
  } catch (error) {
130
- if (__DEV__) {
131
- console.log("[TrialEligibility] Error checking eligibility:", error);
132
- }
133
121
  // On error, default to eligible (better UX)
134
122
  const fallbackMap: TrialEligibilityMap = {};
135
123
  for (const productId of productIds) {
@@ -12,8 +12,6 @@ import {
12
12
  import { SubscriptionManager } from '../../infrastructure/managers/SubscriptionManager';
13
13
  import {
14
14
  SUBSCRIPTION_QUERY_KEYS,
15
- STALE_TIME,
16
- GC_TIME,
17
15
  } from "./subscriptionQueryKeys";
18
16
 
19
17
  /**
@@ -41,11 +39,7 @@ export const useSubscriptionPackages = () => {
41
39
 
42
40
  return SubscriptionManager.getPackages();
43
41
  },
44
- staleTime: STALE_TIME,
45
- gcTime: GC_TIME,
46
42
  enabled: isConfigured,
47
- refetchOnMount: true,
48
- refetchOnWindowFocus: true, // Refetch when app becomes active
49
- refetchOnReconnect: true, // Refetch when network reconnects
43
+
50
44
  });
51
45
  };
@@ -6,8 +6,6 @@
6
6
 
7
7
  export {
8
8
  SUBSCRIPTION_QUERY_KEYS,
9
- STALE_TIME,
10
- GC_TIME,
11
9
  } from "./subscriptionQueryKeys";
12
10
  export { useInitializeSubscription } from "./useInitializeSubscription";
13
11
  export { useSubscriptionPackages } from "./useSubscriptionPackages";
@@ -58,14 +58,6 @@ export class CustomerInfoListenerManager {
58
58
 
59
59
  // Handle renewal (same product, extended expiration)
60
60
  if (renewalResult.isRenewal && config.onRenewalDetected) {
61
- if (__DEV__) {
62
- console.log("[CustomerInfoListener] Renewal detected:", {
63
- userId: this.currentUserId,
64
- productId: renewalResult.productId,
65
- newExpiration: renewalResult.newExpirationDate,
66
- });
67
- }
68
-
69
61
  try {
70
62
  await config.onRenewalDetected(
71
63
  this.currentUserId,
@@ -82,16 +74,6 @@ export class CustomerInfoListenerManager {
82
74
 
83
75
  // Handle plan change (upgrade/downgrade)
84
76
  if (renewalResult.isPlanChange && config.onPlanChanged) {
85
- if (__DEV__) {
86
- console.log("[CustomerInfoListener] Plan change detected:", {
87
- userId: this.currentUserId,
88
- previousProductId: renewalResult.previousProductId,
89
- newProductId: renewalResult.productId,
90
- isUpgrade: renewalResult.isUpgrade,
91
- isDowngrade: renewalResult.isDowngrade,
92
- });
93
- }
94
-
95
77
  try {
96
78
  await config.onPlanChanged(
97
79
  this.currentUserId,
@@ -133,9 +115,6 @@ export class CustomerInfoListenerManager {
133
115
  }
134
116
 
135
117
  destroy(): void {
136
- if (__DEV__) {
137
- console.log('[CustomerInfoListenerManager] Destroying listener manager');
138
- }
139
118
  this.removeListener();
140
119
  this.clearUserId();
141
120
  // Reset renewal state to ensure clean state
@@ -85,15 +85,12 @@ export async function initializeSDK(
85
85
 
86
86
  const key = apiKey || resolveApiKey(deps.config);
87
87
  if (!key) {
88
- if (__DEV__) console.log('[RevenueCat] No API key');
89
88
  return { success: false, offering: null, isPremium: false };
90
89
  }
91
90
 
92
91
  configurationState.configurationInProgress = true;
93
92
  try {
94
93
  configureLogHandler();
95
- if (__DEV__) console.log('[RevenueCat] Configuring:', key.substring(0, 10) + '...');
96
-
97
94
  await Purchases.configure({ apiKey: key, appUserID: userId });
98
95
  configurationState.isPurchasesConfigured = true;
99
96
  deps.setInitialized(true);
@@ -104,14 +101,8 @@ export async function initializeSDK(
104
101
  Purchases.getOfferings(),
105
102
  ]);
106
103
 
107
- if (__DEV__) {
108
- console.log('[RevenueCat] Initialized', {
109
- packages: offerings.current?.availablePackages?.length ?? 0,
110
- });
111
- }
112
104
  return buildSuccessResult(deps, customerInfo, offerings);
113
105
  } catch (error) {
114
- if (__DEV__) console.error('[RevenueCat] Init failed:', error);
115
106
  return { success: false, offering: null, isPremium: false };
116
107
  } finally {
117
108
  configurationState.configurationInProgress = false;
@@ -11,6 +11,8 @@ export class InitializationCache {
11
11
  private initializationInProgress = false;
12
12
  // Track which userId the promise is for (separate from currentUserId which is set after completion)
13
13
  private promiseUserId: string | null = null;
14
+ // Track promise completion state to avoid returning failed promises
15
+ private promiseCompleted = true;
14
16
 
15
17
  /**
16
18
  * Atomically check if reinitialization is needed AND reserve the slot
@@ -22,17 +24,24 @@ export class InitializationCache {
22
24
  return { shouldInit: false, existingPromise: this.initPromise };
23
25
  }
24
26
 
25
- // If already initialized for this user and promise resolved successfully, return it
26
- if (this.initPromise && this.currentUserId === userId && !this.initializationInProgress) {
27
+ // If already initialized for this user and promise completed successfully, return it
28
+ // Only return cached promise if it completed AND it's for the same user
29
+ if (this.initPromise && this.currentUserId === userId && !this.initializationInProgress && this.promiseCompleted) {
27
30
  return { shouldInit: false, existingPromise: this.initPromise };
28
31
  }
29
32
 
30
- // Different user or no initialization - need to reinitialize
31
- // Atomically set the flag
32
- this.initializationInProgress = true;
33
- this.promiseUserId = userId;
33
+ // Different user, no initialization, or failed promise - need to reinitialize
34
+ // Atomically set the flag and clear previous state if needed
35
+ if (!this.initializationInProgress) {
36
+ this.initializationInProgress = true;
37
+ this.promiseUserId = userId;
38
+ this.promiseCompleted = false;
39
+ return { shouldInit: true, existingPromise: null };
40
+ }
34
41
 
35
- return { shouldInit: true, existingPromise: null };
42
+ // If we reach here, initialization is in progress for a different user
43
+ // Wait for current initialization to complete
44
+ return { shouldInit: false, existingPromise: this.initPromise };
36
45
  }
37
46
 
38
47
  setPromise(promise: Promise<boolean>, userId: string): void {
@@ -45,6 +54,7 @@ export class InitializationCache {
45
54
  if (result && this.promiseUserId === userId) {
46
55
  this.currentUserId = userId;
47
56
  }
57
+ this.promiseCompleted = true;
48
58
  return result;
49
59
  })
50
60
  .catch(() => {
@@ -52,7 +62,9 @@ export class InitializationCache {
52
62
  if (this.promiseUserId === userId) {
53
63
  this.initPromise = null;
54
64
  this.promiseUserId = null;
65
+ this.currentUserId = null; // Clear user on failure
55
66
  }
67
+ this.promiseCompleted = true;
56
68
  })
57
69
  .finally(() => {
58
70
  // Always release the mutex
@@ -71,5 +83,6 @@ export class InitializationCache {
71
83
  this.currentUserId = null;
72
84
  this.initializationInProgress = false;
73
85
  this.promiseUserId = null;
86
+ this.promiseCompleted = true;
74
87
  }
75
88
  }
@@ -5,7 +5,7 @@
5
5
 
6
6
  import type { CustomerInfo } from "react-native-purchases";
7
7
  import type { RevenueCatConfig } from "../../core/RevenueCatConfig";
8
- import type { PurchaseSource } from "../../../credits/core/Credits";
8
+ import type { PurchaseSource } from "../../../subscription/core/SubscriptionConstants";
9
9
  import { getPremiumEntitlement } from "../../core/RevenueCatTypes";
10
10
 
11
11
  export async function syncPremiumStatus(
@@ -49,27 +49,12 @@ export async function notifyPurchaseCompleted(
49
49
  customerInfo: CustomerInfo,
50
50
  source?: PurchaseSource
51
51
  ): Promise<void> {
52
- if (__DEV__) {
53
- console.log('[PremiumStatusSyncer] notifyPurchaseCompleted called:', {
54
- userId,
55
- productId,
56
- source,
57
- hasCallback: !!config.onPurchaseCompleted,
58
- });
59
- }
60
-
61
52
  if (!config.onPurchaseCompleted) {
62
- if (__DEV__) {
63
- console.warn('[PremiumStatusSyncer] No onPurchaseCompleted callback configured!');
64
- }
65
53
  return;
66
54
  }
67
55
 
68
56
  try {
69
57
  await config.onPurchaseCompleted(userId, productId, customerInfo, source);
70
- if (__DEV__) {
71
- console.log('[PremiumStatusSyncer] onPurchaseCompleted callback executed successfully');
72
- }
73
58
  } catch (error) {
74
59
  if (__DEV__) {
75
60
  console.error('[PremiumStatusSyncer] onPurchaseCompleted callback failed:', error);