@umituz/react-native-subscription 2.9.6 → 2.10.0
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.
|
|
3
|
+
"version": "2.10.0",
|
|
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
|
@@ -239,6 +239,22 @@ export {
|
|
|
239
239
|
type UseCreditCheckerResult,
|
|
240
240
|
} from "./presentation/hooks/useCreditChecker";
|
|
241
241
|
|
|
242
|
+
export {
|
|
243
|
+
usePaywallVisibility,
|
|
244
|
+
type UsePaywallVisibilityResult,
|
|
245
|
+
} from "./presentation/hooks/usePaywallVisibility";
|
|
246
|
+
|
|
247
|
+
export {
|
|
248
|
+
usePremiumWithConfig,
|
|
249
|
+
type UsePremiumWithConfigParams,
|
|
250
|
+
type UsePremiumWithConfigResult,
|
|
251
|
+
} from "./presentation/hooks/usePremiumWithConfig";
|
|
252
|
+
|
|
253
|
+
export {
|
|
254
|
+
useAuthSubscriptionSync,
|
|
255
|
+
type AuthSubscriptionSyncConfig,
|
|
256
|
+
} from "./presentation/hooks/useAuthSubscriptionSync";
|
|
257
|
+
|
|
242
258
|
// =============================================================================
|
|
243
259
|
// CREDITS SYSTEM - Utilities
|
|
244
260
|
// =============================================================================
|
|
@@ -250,6 +266,12 @@ export {
|
|
|
250
266
|
type CreditChecker,
|
|
251
267
|
} from "./utils/creditChecker";
|
|
252
268
|
|
|
269
|
+
export {
|
|
270
|
+
createAICreditHelpers,
|
|
271
|
+
type AICreditHelpersConfig,
|
|
272
|
+
type AICreditHelpers,
|
|
273
|
+
} from "./utils/aiCreditHelpers";
|
|
274
|
+
|
|
253
275
|
// =============================================================================
|
|
254
276
|
// REVENUECAT - Errors
|
|
255
277
|
// =============================================================================
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useAuthSubscriptionSync Hook
|
|
3
|
+
* Single source of truth for RevenueCat initialization
|
|
4
|
+
* Handles initial setup and auth state transitions
|
|
5
|
+
* Generic implementation for 100+ apps with auth provider abstraction
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { useEffect, useRef, useCallback } from "react";
|
|
9
|
+
|
|
10
|
+
export interface AuthSubscriptionSyncConfig {
|
|
11
|
+
/** Function to subscribe to auth state changes - returns unsubscribe function */
|
|
12
|
+
onAuthStateChanged: (callback: (userId: string | null) => void) => () => void;
|
|
13
|
+
/** Function to initialize subscription for a user */
|
|
14
|
+
initializeSubscription: (userId: string) => Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function useAuthSubscriptionSync(
|
|
18
|
+
config: AuthSubscriptionSyncConfig,
|
|
19
|
+
): void {
|
|
20
|
+
const { onAuthStateChanged, initializeSubscription } = config;
|
|
21
|
+
const previousUserIdRef = useRef<string | null>(null);
|
|
22
|
+
const isInitializedRef = useRef(false);
|
|
23
|
+
|
|
24
|
+
const initialize = useCallback(
|
|
25
|
+
async (userId: string) => {
|
|
26
|
+
await initializeSubscription(userId);
|
|
27
|
+
},
|
|
28
|
+
[initializeSubscription],
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
const unsubscribe = onAuthStateChanged(async (userId: string | null) => {
|
|
33
|
+
if (!userId) {
|
|
34
|
+
previousUserIdRef.current = null;
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const previousUserId = previousUserIdRef.current;
|
|
39
|
+
|
|
40
|
+
if (userId === previousUserId) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (previousUserId && previousUserId !== userId) {
|
|
45
|
+
await initialize(userId);
|
|
46
|
+
} else if (!isInitializedRef.current) {
|
|
47
|
+
await initialize(userId);
|
|
48
|
+
isInitializedRef.current = true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
previousUserIdRef.current = userId;
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
return () => unsubscribe();
|
|
55
|
+
}, [onAuthStateChanged, initialize]);
|
|
56
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Paywall Visibility Hook
|
|
3
|
+
* Simple global state for paywall visibility using module-level state
|
|
4
|
+
* Generic implementation for 100+ apps
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useCallback, useSyncExternalStore } from "react";
|
|
8
|
+
|
|
9
|
+
type Listener = () => void;
|
|
10
|
+
|
|
11
|
+
let paywallVisible = false;
|
|
12
|
+
const listeners = new Set<Listener>();
|
|
13
|
+
|
|
14
|
+
const subscribe = (listener: Listener): (() => void) => {
|
|
15
|
+
listeners.add(listener);
|
|
16
|
+
return () => listeners.delete(listener);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const getSnapshot = (): boolean => paywallVisible;
|
|
20
|
+
|
|
21
|
+
const setPaywallVisible = (visible: boolean): void => {
|
|
22
|
+
paywallVisible = visible;
|
|
23
|
+
listeners.forEach((listener) => listener());
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export interface UsePaywallVisibilityResult {
|
|
27
|
+
showPaywall: boolean;
|
|
28
|
+
setShowPaywall: (visible: boolean) => void;
|
|
29
|
+
openPaywall: () => void;
|
|
30
|
+
closePaywall: () => void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function usePaywallVisibility(): UsePaywallVisibilityResult {
|
|
34
|
+
const showPaywall = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
35
|
+
|
|
36
|
+
const setShowPaywall = useCallback((visible: boolean) => {
|
|
37
|
+
setPaywallVisible(visible);
|
|
38
|
+
}, []);
|
|
39
|
+
|
|
40
|
+
const openPaywall = useCallback(() => {
|
|
41
|
+
setPaywallVisible(true);
|
|
42
|
+
}, []);
|
|
43
|
+
|
|
44
|
+
const closePaywall = useCallback(() => {
|
|
45
|
+
setPaywallVisible(false);
|
|
46
|
+
}, []);
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
showPaywall,
|
|
50
|
+
setShowPaywall,
|
|
51
|
+
openPaywall,
|
|
52
|
+
closePaywall,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* usePremiumWithConfig Hook
|
|
3
|
+
* Premium status from TanStack Query (credits)
|
|
4
|
+
* Subscription state from TanStack Query
|
|
5
|
+
* Generic implementation for 100+ apps with auth provider abstraction
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { useCallback } from "react";
|
|
9
|
+
import type { PurchasesPackage } from "react-native-purchases";
|
|
10
|
+
import { useCredits } from "./useCredits";
|
|
11
|
+
import { useInitializeCredits } from "./useDeductCredit";
|
|
12
|
+
import {
|
|
13
|
+
useSubscriptionPackages,
|
|
14
|
+
usePurchasePackage,
|
|
15
|
+
useRestorePurchase,
|
|
16
|
+
} from "../../revenuecat/presentation/hooks/useSubscriptionQueries";
|
|
17
|
+
import { usePaywallVisibility } from "./usePaywallVisibility";
|
|
18
|
+
|
|
19
|
+
export interface UsePremiumWithConfigParams {
|
|
20
|
+
/** Function to get current user ID (can be from Firebase, Supabase, etc.) */
|
|
21
|
+
getUserId: () => string | null | undefined;
|
|
22
|
+
/** Enable/disable credits query */
|
|
23
|
+
enabled?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface UsePremiumWithConfigResult {
|
|
27
|
+
isPremium: boolean;
|
|
28
|
+
isLoading: boolean;
|
|
29
|
+
packages: PurchasesPackage[];
|
|
30
|
+
credits: any; // UserCredits from subscription package
|
|
31
|
+
showPaywall: boolean;
|
|
32
|
+
purchasePackage: (pkg: PurchasesPackage) => Promise<boolean>;
|
|
33
|
+
restorePurchase: () => Promise<boolean>;
|
|
34
|
+
setShowPaywall: (show: boolean) => void;
|
|
35
|
+
closePaywall: () => void;
|
|
36
|
+
openPaywall: () => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const usePremiumWithConfig = (
|
|
40
|
+
params: UsePremiumWithConfigParams,
|
|
41
|
+
): UsePremiumWithConfigResult => {
|
|
42
|
+
const { getUserId, enabled = true } = params;
|
|
43
|
+
const userId = getUserId();
|
|
44
|
+
|
|
45
|
+
const { credits, isLoading: creditsLoading } = useCredits({
|
|
46
|
+
userId,
|
|
47
|
+
enabled: enabled && !!userId,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const { initializeCredits } = useInitializeCredits({ userId });
|
|
51
|
+
|
|
52
|
+
const { data: packages = [], isLoading: packagesLoading } =
|
|
53
|
+
useSubscriptionPackages(userId);
|
|
54
|
+
const purchaseMutation = usePurchasePackage(userId);
|
|
55
|
+
const restoreMutation = useRestorePurchase(userId);
|
|
56
|
+
|
|
57
|
+
const { showPaywall, setShowPaywall, closePaywall, openPaywall } =
|
|
58
|
+
usePaywallVisibility();
|
|
59
|
+
|
|
60
|
+
const isPremium = credits !== null;
|
|
61
|
+
|
|
62
|
+
const handlePurchase = useCallback(
|
|
63
|
+
async (pkg: PurchasesPackage): Promise<boolean> => {
|
|
64
|
+
const success = await purchaseMutation.mutateAsync(pkg);
|
|
65
|
+
if (success && userId) {
|
|
66
|
+
await initializeCredits();
|
|
67
|
+
}
|
|
68
|
+
return success;
|
|
69
|
+
},
|
|
70
|
+
[purchaseMutation, userId, initializeCredits],
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const handleRestore = useCallback(async (): Promise<boolean> => {
|
|
74
|
+
const success = await restoreMutation.mutateAsync();
|
|
75
|
+
if (success && userId) {
|
|
76
|
+
await initializeCredits();
|
|
77
|
+
}
|
|
78
|
+
return success;
|
|
79
|
+
}, [restoreMutation, userId, initializeCredits]);
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
isPremium,
|
|
83
|
+
isLoading:
|
|
84
|
+
creditsLoading ||
|
|
85
|
+
packagesLoading ||
|
|
86
|
+
purchaseMutation.isPending ||
|
|
87
|
+
restoreMutation.isPending,
|
|
88
|
+
packages,
|
|
89
|
+
credits,
|
|
90
|
+
showPaywall,
|
|
91
|
+
purchasePackage: handlePurchase,
|
|
92
|
+
restorePurchase: handleRestore,
|
|
93
|
+
setShowPaywall,
|
|
94
|
+
closePaywall,
|
|
95
|
+
openPaywall,
|
|
96
|
+
};
|
|
97
|
+
};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Credit Helpers
|
|
3
|
+
*
|
|
4
|
+
* Common patterns for AI generation apps to handle credits.
|
|
5
|
+
* Provides ready-to-use functions for credit checking and deduction.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* import { createAICreditHelpers } from '@umituz/react-native-subscription';
|
|
9
|
+
*
|
|
10
|
+
* const helpers = createAICreditHelpers({
|
|
11
|
+
* repository,
|
|
12
|
+
* imageGenerationTypes: ['future_image', 'santa_transform'],
|
|
13
|
+
* onCreditDeducted: (userId) => invalidateCache(userId)
|
|
14
|
+
* });
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type { CreditType } from "../domain/entities/Credits";
|
|
18
|
+
import type { CreditsRepository } from "../infrastructure/repositories/CreditsRepository";
|
|
19
|
+
import { createCreditChecker } from "./creditChecker";
|
|
20
|
+
|
|
21
|
+
export interface AICreditHelpersConfig {
|
|
22
|
+
/**
|
|
23
|
+
* Credits repository instance
|
|
24
|
+
*/
|
|
25
|
+
repository: CreditsRepository;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* List of operation types that should use "image" credits.
|
|
29
|
+
* All other types will use "text" credits.
|
|
30
|
+
* @example ['future_image', 'santa_transform', 'photo_generation']
|
|
31
|
+
*/
|
|
32
|
+
imageGenerationTypes: string[];
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Optional callback called after successful credit deduction.
|
|
36
|
+
* Use this to invalidate TanStack Query cache or trigger UI updates.
|
|
37
|
+
*/
|
|
38
|
+
onCreditDeducted?: (userId: string, creditType: CreditType) => void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface AICreditHelpers {
|
|
42
|
+
/**
|
|
43
|
+
* Check if user has credits for a specific generation type
|
|
44
|
+
* @param userId - User ID
|
|
45
|
+
* @param generationType - Type of generation (e.g., 'future_image', 'text_summary')
|
|
46
|
+
* @returns boolean indicating if credits are available
|
|
47
|
+
*/
|
|
48
|
+
checkCreditsForGeneration: (
|
|
49
|
+
userId: string | undefined,
|
|
50
|
+
generationType: string
|
|
51
|
+
) => Promise<boolean>;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Deduct credits after successful generation
|
|
55
|
+
* @param userId - User ID
|
|
56
|
+
* @param generationType - Type of generation that was performed
|
|
57
|
+
*/
|
|
58
|
+
deductCreditsForGeneration: (
|
|
59
|
+
userId: string | undefined,
|
|
60
|
+
generationType: string
|
|
61
|
+
) => Promise<void>;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get credit type for a generation type (useful for UI display)
|
|
65
|
+
* @param generationType - Type of generation
|
|
66
|
+
* @returns "image" or "text"
|
|
67
|
+
*/
|
|
68
|
+
getCreditType: (generationType: string) => CreditType;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Creates AI-specific credit helper functions
|
|
73
|
+
*/
|
|
74
|
+
export function createAICreditHelpers(
|
|
75
|
+
config: AICreditHelpersConfig
|
|
76
|
+
): AICreditHelpers {
|
|
77
|
+
const { repository, imageGenerationTypes, onCreditDeducted } = config;
|
|
78
|
+
|
|
79
|
+
// Map generation type to credit type
|
|
80
|
+
const getCreditType = (generationType: string): CreditType => {
|
|
81
|
+
return imageGenerationTypes.includes(generationType) ? "image" : "text";
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// Create credit checker with the mapping
|
|
85
|
+
const checker = createCreditChecker({
|
|
86
|
+
repository,
|
|
87
|
+
getCreditType,
|
|
88
|
+
onCreditDeducted,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Check if credits are available for generation
|
|
92
|
+
const checkCreditsForGeneration = async (
|
|
93
|
+
userId: string | undefined,
|
|
94
|
+
generationType: string
|
|
95
|
+
): Promise<boolean> => {
|
|
96
|
+
const result = await checker.checkCreditsAvailable(userId, generationType);
|
|
97
|
+
return result.success;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// Deduct credits after successful generation
|
|
101
|
+
const deductCreditsForGeneration = async (
|
|
102
|
+
userId: string | undefined,
|
|
103
|
+
generationType: string
|
|
104
|
+
): Promise<void> => {
|
|
105
|
+
const creditType = getCreditType(generationType);
|
|
106
|
+
await checker.deductCreditsAfterSuccess(userId, creditType);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
checkCreditsForGeneration,
|
|
111
|
+
deductCreditsForGeneration,
|
|
112
|
+
getCreditType,
|
|
113
|
+
};
|
|
114
|
+
}
|