@umituz/react-native-subscription 2.24.2 → 2.24.4
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.4",
|
|
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",
|
|
@@ -99,7 +99,26 @@ export function useFeatureGate(
|
|
|
99
99
|
|
|
100
100
|
// Step 1: Auth check
|
|
101
101
|
if (!authGate.requireAuth(() => {})) {
|
|
102
|
-
|
|
102
|
+
// Wrap action to re-check credits after auth succeeds
|
|
103
|
+
const postAuthAction = () => {
|
|
104
|
+
// Step 2: Subscription check (bypasses credits if subscribed)
|
|
105
|
+
if (hasSubscription) {
|
|
106
|
+
action();
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Step 3: Credits check
|
|
111
|
+
if (!hasCredits) {
|
|
112
|
+
pendingActionRef.current = action;
|
|
113
|
+
isWaitingForPurchaseRef.current = true;
|
|
114
|
+
onShowPaywall(requiredCredits);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// All checks passed
|
|
119
|
+
action();
|
|
120
|
+
};
|
|
121
|
+
onShowAuthModal(postAuthAction);
|
|
103
122
|
return;
|
|
104
123
|
}
|
|
105
124
|
|
|
@@ -124,7 +143,10 @@ export function useFeatureGate(
|
|
|
124
143
|
authGate,
|
|
125
144
|
creditsGate,
|
|
126
145
|
hasSubscription,
|
|
146
|
+
hasCredits,
|
|
147
|
+
requiredCredits,
|
|
127
148
|
onShowAuthModal,
|
|
149
|
+
onShowPaywall,
|
|
128
150
|
]
|
|
129
151
|
);
|
|
130
152
|
|
|
@@ -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,
|