@umituz/react-native-subscription 2.24.1 → 2.24.3

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.24.1",
3
+ "version": "2.24.3",
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",
@@ -5,8 +5,6 @@
5
5
  * Generic and reusable - uses config from module-level provider.
6
6
  */
7
7
 
8
- declare const __DEV__: boolean;
9
-
10
8
  import { useQuery } from "@umituz/react-native-design-system";
11
9
  import { useCallback, useMemo } from "react";
12
10
  import type { UserCredits } from "../../domain/entities/Credits";
@@ -65,37 +63,23 @@ export const useCredits = ({
65
63
 
66
64
  const queryEnabled = enabled && !!userId && isConfigured;
67
65
 
68
- if (__DEV__) {
69
- console.log("[useCredits] Query state:", {
70
- userId: userId?.slice(0, 8),
71
- enabled,
72
- isConfigured,
73
- queryEnabled,
74
- });
75
- }
76
-
77
66
  const { data, isLoading, error, refetch } = useQuery({
78
67
  queryKey: creditsQueryKeys.user(userId ?? ""),
79
68
  queryFn: async () => {
80
69
  if (!userId || !isConfigured) {
81
- if (__DEV__) console.log("[useCredits] Query skipped:", { hasUserId: !!userId, isConfigured });
82
70
  return null;
83
71
  }
84
- if (__DEV__) console.log("[useCredits] Executing queryFn for userId:", userId.slice(0, 8));
85
72
  const repository = getCreditsRepository();
86
73
  const result = await repository.getCredits(userId);
87
74
  if (!result.success) {
88
- if (__DEV__) console.error("[useCredits] Query failed:", result.error?.message);
89
75
  throw new Error(result.error?.message || "Failed to fetch credits");
90
76
  }
91
77
 
92
78
  // Background sync: If mapper detected expired status, sync to Firestore
93
79
  if (result.data?.status === "expired") {
94
- if (__DEV__) console.log("[useCredits] Detected expired subscription, syncing...");
95
80
  repository.syncExpiredStatus(userId).catch(() => {});
96
81
  }
97
82
 
98
- if (__DEV__) console.log("[useCredits] Query success:", { hasData: !!result.data, credits: result.data?.credits, status: result.data?.status });
99
83
  return result.data || null;
100
84
  },
101
85
  enabled: queryEnabled,
@@ -21,8 +21,6 @@
21
21
 
22
22
  import { useCallback } from "react";
23
23
 
24
- declare const __DEV__: boolean;
25
-
26
24
  export interface UseCreditsGateParams {
27
25
  /** Whether user has enough credits for the action */
28
26
  hasCredits: boolean;
@@ -52,27 +50,12 @@ export function useCreditsGate(
52
50
  const requireCredits = useCallback(
53
51
  (_action: () => void | Promise<void>): boolean => {
54
52
  if (!hasCredits) {
55
- if (__DEV__) {
56
-
57
- console.log("[useCreditsGate] Insufficient credits", {
58
- creditBalance,
59
- requiredCredits,
60
- });
61
- }
62
53
  onCreditsRequired(requiredCredits);
63
54
  return false;
64
55
  }
65
-
66
- if (__DEV__) {
67
-
68
- console.log("[useCreditsGate] Has credits, proceeding", {
69
- creditBalance,
70
- requiredCredits,
71
- });
72
- }
73
56
  return true;
74
57
  },
75
- [hasCredits, creditBalance, requiredCredits, onCreditsRequired]
58
+ [hasCredits, requiredCredits, onCreditsRequired]
76
59
  );
77
60
 
78
61
  return {
@@ -3,7 +3,7 @@
3
3
  * Automatically executes saved purchase when user converts from anonymous to authenticated
4
4
  */
5
5
 
6
- import { useEffect, useRef, useCallback } from "react";
6
+ import { useEffect, useRef } from "react";
7
7
  import { getSavedPurchase, clearSavedPurchase } from "./useAuthAwarePurchase";
8
8
  import { usePremium } from "./usePremium";
9
9
  import { SubscriptionManager } from "../../revenuecat";
@@ -31,97 +31,35 @@ export const useSavedPurchaseAutoExecution = (
31
31
 
32
32
  const prevIsAnonymousRef = useRef<boolean | undefined>(undefined);
33
33
  const isExecutingRef = useRef(false);
34
+ const hasExecutedRef = useRef(false);
34
35
 
35
- const executeWithWait = useCallback(async () => {
36
- const savedPurchase = getSavedPurchase();
37
- if (!savedPurchase || !userId) return;
38
-
39
- if (__DEV__) {
40
- console.log(
41
- "[SavedPurchaseAutoExecution] Waiting for RevenueCat initialization...",
42
- { userId: userId.slice(0, 8), productId: savedPurchase.pkg.product.identifier }
43
- );
44
- }
45
-
46
- isExecutingRef.current = true;
47
-
48
- const maxAttempts = 20;
49
- const delayMs = 500;
50
-
51
- for (let attempt = 0; attempt < maxAttempts; attempt++) {
52
- const isReady = SubscriptionManager.isInitializedForUser(userId);
53
-
54
- if (__DEV__) {
55
- console.log(
56
- `[SavedPurchaseAutoExecution] Attempt ${attempt + 1}/${maxAttempts}, isReady: ${isReady}`
57
- );
58
- }
59
-
60
- if (isReady) {
61
- const pkg = savedPurchase.pkg;
62
- clearSavedPurchase();
63
-
64
- if (__DEV__) {
65
- console.log(
66
- "[SavedPurchaseAutoExecution] RevenueCat ready, starting purchase...",
67
- { productId: pkg.product.identifier, userId: userId.slice(0, 8) }
68
- );
69
- }
70
-
71
- // Start global loading state
72
- startPurchase(pkg.product.identifier, "auto-execution");
36
+ // Store callbacks in refs to avoid dependency changes
37
+ const purchasePackageRef = useRef(purchasePackage);
38
+ const onSuccessRef = useRef(onSuccess);
39
+ const onErrorRef = useRef(onError);
40
+ const startPurchaseRef = useRef(startPurchase);
41
+ const endPurchaseRef = useRef(endPurchase);
73
42
 
74
- try {
75
- if (__DEV__) {
76
- console.log("[SavedPurchaseAutoExecution] Calling purchasePackage...");
77
- }
78
-
79
- const success = await purchasePackage(pkg);
80
-
81
- if (__DEV__) {
82
- console.log("[SavedPurchaseAutoExecution] Purchase completed:", {
83
- success,
84
- productId: pkg.product.identifier
85
- });
86
- }
87
-
88
- if (success && onSuccess) {
89
- onSuccess();
90
- }
91
- } catch (error) {
92
- if (__DEV__) {
93
- console.error("[SavedPurchaseAutoExecution] Purchase error:", {
94
- error,
95
- productId: pkg.product.identifier
96
- });
97
- }
98
- if (onError && error instanceof Error) {
99
- onError(error);
100
- }
101
- } finally {
102
- // End global loading state
103
- endPurchase();
104
- isExecutingRef.current = false;
43
+ // Update refs when values change
44
+ useEffect(() => {
45
+ purchasePackageRef.current = purchasePackage;
46
+ }, [purchasePackage]);
105
47
 
106
- if (__DEV__) {
107
- console.log("[SavedPurchaseAutoExecution] Purchase flow finished");
108
- }
109
- }
48
+ useEffect(() => {
49
+ onSuccessRef.current = onSuccess;
50
+ }, [onSuccess]);
110
51
 
111
- return;
112
- }
52
+ useEffect(() => {
53
+ onErrorRef.current = onError;
54
+ }, [onError]);
113
55
 
114
- await new Promise((resolve) => setTimeout(resolve, delayMs));
115
- }
56
+ useEffect(() => {
57
+ startPurchaseRef.current = startPurchase;
58
+ }, [startPurchase]);
116
59
 
117
- if (__DEV__) {
118
- console.log(
119
- "[SavedPurchaseAutoExecution] Timeout waiting for RevenueCat, clearing saved purchase"
120
- );
121
- }
122
- clearSavedPurchase();
123
- isExecutingRef.current = false;
124
- }, [userId, purchasePackage, onSuccess, onError, startPurchase, endPurchase]);
60
+ useEffect(() => {
61
+ endPurchaseRef.current = endPurchase;
62
+ }, [endPurchase]);
125
63
 
126
64
  useEffect(() => {
127
65
  const isAuthenticated = !!userId && !isAnonymous;
@@ -131,7 +69,10 @@ export const useSavedPurchaseAutoExecution = (
131
69
  const wasAnonymous = prevIsAnonymous === true;
132
70
  const becameAuthenticated = wasAnonymous && isAuthenticated;
133
71
 
134
- if (__DEV__) {
72
+ // Only log when there's a state change worth noting
73
+ const shouldLog = prevIsAnonymousRef.current !== isAnonymous;
74
+
75
+ if (__DEV__ && shouldLog) {
135
76
  console.log("[SavedPurchaseAutoExecution] Auth state check:", {
136
77
  userId: userId?.slice(0, 8),
137
78
  prevIsAnonymous,
@@ -141,19 +82,127 @@ export const useSavedPurchaseAutoExecution = (
141
82
  becameAuthenticated,
142
83
  hasSavedPurchase: !!savedPurchase,
143
84
  savedProductId: savedPurchase?.pkg.product.identifier,
144
- willExecute: becameAuthenticated && !!savedPurchase && !isExecutingRef.current,
85
+ willExecute:
86
+ becameAuthenticated &&
87
+ !!savedPurchase &&
88
+ !isExecutingRef.current &&
89
+ !hasExecutedRef.current,
145
90
  });
146
91
  }
147
92
 
148
- if (becameAuthenticated && savedPurchase && !isExecutingRef.current) {
93
+ // Execute only once when transitioning from anonymous to authenticated
94
+ if (
95
+ becameAuthenticated &&
96
+ savedPurchase &&
97
+ !isExecutingRef.current &&
98
+ !hasExecutedRef.current
99
+ ) {
149
100
  if (__DEV__) {
150
101
  console.log("[SavedPurchaseAutoExecution] Triggering auto-execution...");
151
102
  }
152
- executeWithWait();
103
+
104
+ hasExecutedRef.current = true;
105
+ isExecutingRef.current = true;
106
+
107
+ // Execute purchase flow
108
+ const executeFlow = async () => {
109
+ const currentUserId = userId;
110
+ if (!currentUserId) {
111
+ isExecutingRef.current = false;
112
+ return;
113
+ }
114
+
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
+ const maxAttempts = 20;
126
+ const delayMs = 500;
127
+
128
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
129
+ const isReady = SubscriptionManager.isInitializedForUser(currentUserId);
130
+
131
+ if (__DEV__) {
132
+ console.log(
133
+ `[SavedPurchaseAutoExecution] Attempt ${attempt + 1}/${maxAttempts}, isReady: ${isReady}`
134
+ );
135
+ }
136
+
137
+ if (isReady) {
138
+ const pkg = savedPurchase.pkg;
139
+ clearSavedPurchase();
140
+
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
+ startPurchaseRef.current(pkg.product.identifier, "auto-execution");
149
+
150
+ try {
151
+ if (__DEV__) {
152
+ console.log("[SavedPurchaseAutoExecution] Calling purchasePackage...");
153
+ }
154
+
155
+ const success = await purchasePackageRef.current(pkg);
156
+
157
+ if (__DEV__) {
158
+ console.log("[SavedPurchaseAutoExecution] Purchase completed:", {
159
+ success,
160
+ productId: pkg.product.identifier,
161
+ });
162
+ }
163
+
164
+ if (success && onSuccessRef.current) {
165
+ onSuccessRef.current();
166
+ }
167
+ } catch (error) {
168
+ if (__DEV__) {
169
+ console.error("[SavedPurchaseAutoExecution] Purchase error:", {
170
+ error,
171
+ productId: pkg.product.identifier,
172
+ });
173
+ }
174
+ if (onErrorRef.current && error instanceof Error) {
175
+ onErrorRef.current(error);
176
+ }
177
+ } finally {
178
+ endPurchaseRef.current();
179
+ isExecutingRef.current = false;
180
+
181
+ if (__DEV__) {
182
+ console.log("[SavedPurchaseAutoExecution] Purchase flow finished");
183
+ }
184
+ }
185
+
186
+ return;
187
+ }
188
+
189
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
190
+ }
191
+
192
+ if (__DEV__) {
193
+ console.log(
194
+ "[SavedPurchaseAutoExecution] Timeout waiting for RevenueCat, clearing saved purchase"
195
+ );
196
+ }
197
+ clearSavedPurchase();
198
+ isExecutingRef.current = false;
199
+ };
200
+
201
+ executeFlow();
153
202
  }
154
203
 
155
204
  prevIsAnonymousRef.current = isAnonymous;
156
- }, [userId, isAnonymous, executeWithWait]);
205
+ }, [userId, isAnonymous]);
157
206
 
158
207
  return {
159
208
  isExecuting: isExecutingRef.current,