@umituz/react-native-subscription 2.13.10 → 2.13.12
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.13.
|
|
3
|
+
"version": "2.13.12",
|
|
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",
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auth-Aware Purchase Hook
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
* Uses globally configured auth provider
|
|
4
|
+
* Configure once at app start with configureAuthProvider()
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { useCallback } from "react";
|
|
@@ -13,52 +13,78 @@ export interface PurchaseAuthProvider {
|
|
|
13
13
|
showAuthModal: () => void;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
// Global auth provider - configured once at app start
|
|
17
|
+
let globalAuthProvider: PurchaseAuthProvider | null = null;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Configure auth provider for purchases
|
|
21
|
+
* Call this once at app initialization
|
|
22
|
+
*/
|
|
23
|
+
export const configureAuthProvider = (provider: PurchaseAuthProvider): void => {
|
|
24
|
+
globalAuthProvider = provider;
|
|
25
|
+
if (__DEV__) {
|
|
26
|
+
console.log("[useAuthAwarePurchase] Auth provider configured");
|
|
27
|
+
}
|
|
28
|
+
};
|
|
19
29
|
|
|
20
30
|
export interface UseAuthAwarePurchaseResult {
|
|
21
31
|
handlePurchase: (pkg: PurchasesPackage) => Promise<boolean>;
|
|
22
32
|
handleRestore: () => Promise<boolean>;
|
|
23
33
|
}
|
|
24
34
|
|
|
25
|
-
export const useAuthAwarePurchase = ({
|
|
26
|
-
authProvider,
|
|
27
|
-
}: UseAuthAwarePurchaseParams): UseAuthAwarePurchaseResult => {
|
|
35
|
+
export const useAuthAwarePurchase = (): UseAuthAwarePurchaseResult => {
|
|
28
36
|
const { purchasePackage, restorePurchase, closePaywall } = usePremium();
|
|
29
37
|
|
|
30
38
|
const handlePurchase = useCallback(
|
|
31
39
|
async (pkg: PurchasesPackage): Promise<boolean> => {
|
|
32
|
-
if (!
|
|
40
|
+
if (!globalAuthProvider) {
|
|
41
|
+
if (__DEV__) {
|
|
42
|
+
console.warn(
|
|
43
|
+
"[useAuthAwarePurchase] Auth provider not configured. Call configureAuthProvider() at app start.",
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
return purchasePackage(pkg);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!globalAuthProvider.isAuthenticated()) {
|
|
33
50
|
if (__DEV__) {
|
|
34
51
|
console.log(
|
|
35
52
|
"[useAuthAwarePurchase] User not authenticated, opening auth modal",
|
|
36
53
|
);
|
|
37
54
|
}
|
|
38
55
|
closePaywall();
|
|
39
|
-
|
|
56
|
+
globalAuthProvider.showAuthModal();
|
|
40
57
|
return false;
|
|
41
58
|
}
|
|
42
59
|
|
|
43
60
|
return purchasePackage(pkg);
|
|
44
61
|
},
|
|
45
|
-
[purchasePackage, closePaywall
|
|
62
|
+
[purchasePackage, closePaywall],
|
|
46
63
|
);
|
|
47
64
|
|
|
48
65
|
const handleRestore = useCallback(async (): Promise<boolean> => {
|
|
49
|
-
if (!
|
|
66
|
+
if (!globalAuthProvider) {
|
|
67
|
+
if (__DEV__) {
|
|
68
|
+
console.warn(
|
|
69
|
+
"[useAuthAwarePurchase] Auth provider not configured. Call configureAuthProvider() at app start.",
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
return restorePurchase();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!globalAuthProvider.isAuthenticated()) {
|
|
50
76
|
if (__DEV__) {
|
|
51
77
|
console.log(
|
|
52
78
|
"[useAuthAwarePurchase] User not authenticated, opening auth modal",
|
|
53
79
|
);
|
|
54
80
|
}
|
|
55
81
|
closePaywall();
|
|
56
|
-
|
|
82
|
+
globalAuthProvider.showAuthModal();
|
|
57
83
|
return false;
|
|
58
84
|
}
|
|
59
85
|
|
|
60
86
|
return restorePurchase();
|
|
61
|
-
}, [restorePurchase, closePaywall
|
|
87
|
+
}, [restorePurchase, closePaywall]);
|
|
62
88
|
|
|
63
89
|
return {
|
|
64
90
|
handlePurchase,
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
* useFeatureGate Hook
|
|
3
3
|
*
|
|
4
4
|
* Feature gating with TanStack Query for server state.
|
|
5
|
-
* Checks auth
|
|
5
|
+
* Checks auth, premium status, AND credit balance before allowing actions.
|
|
6
6
|
*
|
|
7
7
|
* Flow:
|
|
8
8
|
* 1. NOT authenticated → onShowAuthModal(callback)
|
|
9
|
-
* 2. Authenticated but
|
|
10
|
-
* 3. Authenticated
|
|
9
|
+
* 2. Authenticated but no credits → onShowPaywall()
|
|
10
|
+
* 3. Authenticated with credits → Execute action
|
|
11
11
|
*
|
|
12
12
|
* @example
|
|
13
13
|
* ```typescript
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* isAuthenticated: !!user,
|
|
17
17
|
* onShowAuthModal: (cb) => authModal.show(cb),
|
|
18
18
|
* onShowPaywall: () => setShowPaywall(true),
|
|
19
|
+
* creditType: 'image', // or 'text'
|
|
19
20
|
* });
|
|
20
21
|
*
|
|
21
22
|
* // Gate a premium feature
|
|
@@ -27,6 +28,7 @@
|
|
|
27
28
|
|
|
28
29
|
import { useCallback } from "react";
|
|
29
30
|
import { useCredits } from "./useCredits";
|
|
31
|
+
import type { CreditType } from "../../domain/entities/Credits";
|
|
30
32
|
|
|
31
33
|
declare const __DEV__: boolean;
|
|
32
34
|
|
|
@@ -39,67 +41,107 @@ export interface UseFeatureGateParams {
|
|
|
39
41
|
onShowAuthModal: (pendingCallback: () => void | Promise<void>) => void;
|
|
40
42
|
/** Callback to show paywall */
|
|
41
43
|
onShowPaywall: () => void;
|
|
44
|
+
/** Credit type to check (default: 'image') */
|
|
45
|
+
creditType?: CreditType;
|
|
42
46
|
}
|
|
43
47
|
|
|
44
48
|
export interface UseFeatureGateResult {
|
|
45
|
-
/** Gate a feature - checks auth first, then
|
|
49
|
+
/** Gate a feature - checks auth first, then credits balance */
|
|
46
50
|
requireFeature: (action: () => void | Promise<void>) => void;
|
|
47
51
|
/** Whether user is authenticated */
|
|
48
52
|
isAuthenticated: boolean;
|
|
49
|
-
/** Whether user has
|
|
50
|
-
|
|
53
|
+
/** Whether user has credits remaining */
|
|
54
|
+
hasCredits: boolean;
|
|
51
55
|
/** Whether feature access is allowed */
|
|
52
56
|
canAccess: boolean;
|
|
53
57
|
/** Loading state */
|
|
54
58
|
isLoading: boolean;
|
|
59
|
+
/** Current credit balance for the specified type */
|
|
60
|
+
creditBalance: number;
|
|
55
61
|
}
|
|
56
62
|
|
|
57
63
|
export function useFeatureGate(
|
|
58
64
|
params: UseFeatureGateParams
|
|
59
65
|
): UseFeatureGateResult {
|
|
60
|
-
const {
|
|
66
|
+
const {
|
|
67
|
+
userId,
|
|
68
|
+
isAuthenticated,
|
|
69
|
+
onShowAuthModal,
|
|
70
|
+
onShowPaywall,
|
|
71
|
+
creditType = "image",
|
|
72
|
+
} = params;
|
|
61
73
|
|
|
62
74
|
// Use TanStack Query to get credits (server state)
|
|
63
|
-
const { credits, isLoading } = useCredits({
|
|
75
|
+
const { credits, isLoading, hasImageCredits, hasTextCredits } = useCredits({
|
|
64
76
|
userId,
|
|
65
77
|
enabled: isAuthenticated && !!userId,
|
|
66
78
|
});
|
|
67
79
|
|
|
68
|
-
//
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
80
|
+
// Check actual credit balance, not just existence
|
|
81
|
+
const hasCredits = creditType === "image" ? hasImageCredits : hasTextCredits;
|
|
82
|
+
const creditBalance =
|
|
83
|
+
creditType === "image"
|
|
84
|
+
? credits?.imageCredits ?? 0
|
|
85
|
+
: credits?.textCredits ?? 0;
|
|
72
86
|
|
|
73
87
|
const requireFeature = useCallback(
|
|
74
88
|
(action: () => void | Promise<void>) => {
|
|
89
|
+
if (__DEV__) {
|
|
90
|
+
console.log("[useFeatureGate] requireFeature called", {
|
|
91
|
+
isAuthenticated,
|
|
92
|
+
hasCredits,
|
|
93
|
+
creditBalance,
|
|
94
|
+
creditType,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
75
98
|
// Step 1: Check authentication
|
|
76
99
|
if (!isAuthenticated) {
|
|
100
|
+
if (__DEV__) {
|
|
101
|
+
console.log("[useFeatureGate] Not authenticated, showing auth modal");
|
|
102
|
+
}
|
|
77
103
|
onShowAuthModal(() => {
|
|
78
104
|
// We NO LONGER call action() blindly here.
|
|
79
105
|
// The component will re-render with the new auth state,
|
|
80
106
|
// and the user should be allowed to try the action again.
|
|
81
|
-
// This avoids executing actions before credits are loaded or verified.
|
|
82
107
|
});
|
|
83
108
|
return;
|
|
84
109
|
}
|
|
85
110
|
|
|
86
|
-
// Step 2: Check
|
|
87
|
-
if (!
|
|
111
|
+
// Step 2: Check credit balance (not just existence)
|
|
112
|
+
if (!hasCredits) {
|
|
113
|
+
if (__DEV__) {
|
|
114
|
+
console.log("[useFeatureGate] No credits, showing paywall", {
|
|
115
|
+
creditBalance,
|
|
116
|
+
creditType,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
88
119
|
onShowPaywall();
|
|
89
120
|
return;
|
|
90
121
|
}
|
|
91
122
|
|
|
92
|
-
// Step 3: User is authenticated
|
|
123
|
+
// Step 3: User is authenticated with credits - execute action
|
|
124
|
+
if (__DEV__) {
|
|
125
|
+
console.log("[useFeatureGate] Access granted, executing action");
|
|
126
|
+
}
|
|
93
127
|
action();
|
|
94
128
|
},
|
|
95
|
-
[
|
|
129
|
+
[
|
|
130
|
+
isAuthenticated,
|
|
131
|
+
hasCredits,
|
|
132
|
+
creditBalance,
|
|
133
|
+
creditType,
|
|
134
|
+
onShowAuthModal,
|
|
135
|
+
onShowPaywall,
|
|
136
|
+
]
|
|
96
137
|
);
|
|
97
138
|
|
|
98
139
|
return {
|
|
99
140
|
requireFeature,
|
|
100
141
|
isAuthenticated,
|
|
101
|
-
|
|
102
|
-
canAccess: isAuthenticated &&
|
|
142
|
+
hasCredits,
|
|
143
|
+
canAccess: isAuthenticated && hasCredits,
|
|
103
144
|
isLoading,
|
|
145
|
+
creditBalance,
|
|
104
146
|
};
|
|
105
147
|
}
|