@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.
|
|
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,
|
|
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
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
onSuccessRef.current = onSuccess;
|
|
50
|
+
}, [onSuccess]);
|
|
110
51
|
|
|
111
|
-
|
|
112
|
-
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
onErrorRef.current = onError;
|
|
54
|
+
}, [onError]);
|
|
113
55
|
|
|
114
|
-
|
|
115
|
-
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
startPurchaseRef.current = startPurchase;
|
|
58
|
+
}, [startPurchase]);
|
|
116
59
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
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:
|
|
85
|
+
willExecute:
|
|
86
|
+
becameAuthenticated &&
|
|
87
|
+
!!savedPurchase &&
|
|
88
|
+
!isExecutingRef.current &&
|
|
89
|
+
!hasExecutedRef.current,
|
|
145
90
|
});
|
|
146
91
|
}
|
|
147
92
|
|
|
148
|
-
|
|
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
|
-
|
|
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
|
|
205
|
+
}, [userId, isAnonymous]);
|
|
157
206
|
|
|
158
207
|
return {
|
|
159
208
|
isExecuting: isExecutingRef.current,
|