@umituz/react-native-subscription 2.26.14 → 2.26.15

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.
Files changed (39) hide show
  1. package/package.json +1 -1
  2. package/src/domains/paywall/components/PaywallModal.tsx +36 -13
  3. package/src/domains/paywall/components/PlanCard.tsx +16 -3
  4. package/src/domains/paywall/entities/types.ts +4 -0
  5. package/src/domains/paywall/hooks/usePaywallTranslations.ts +8 -0
  6. package/src/presentation/hooks/index.ts +0 -15
  7. package/src/presentation/hooks/useFeatureGate.ts +12 -33
  8. package/src/revenuecat/domain/types/RevenueCatTypes.ts +32 -0
  9. package/src/revenuecat/index.ts +1 -0
  10. package/src/revenuecat/presentation/hooks/useRevenueCatTrialEligibility.ts +179 -0
  11. package/src/presentation/hooks/useAuthAwarePurchase.md +0 -92
  12. package/src/presentation/hooks/useAuthAwarePurchase.ts +0 -138
  13. package/src/presentation/hooks/useAuthGate.md +0 -89
  14. package/src/presentation/hooks/useAuthGate.ts +0 -65
  15. package/src/presentation/hooks/useCreditChecker.md +0 -102
  16. package/src/presentation/hooks/useCreditChecker.ts +0 -41
  17. package/src/presentation/hooks/useCreditsGate.md +0 -94
  18. package/src/presentation/hooks/useCreditsGate.ts +0 -67
  19. package/src/presentation/hooks/useDevTestCallbacks.md +0 -91
  20. package/src/presentation/hooks/useDevTestCallbacks.ts +0 -142
  21. package/src/presentation/hooks/useInitializeCredits.md +0 -92
  22. package/src/presentation/hooks/useInitializeCredits.ts +0 -57
  23. package/src/presentation/hooks/usePremiumGate.md +0 -88
  24. package/src/presentation/hooks/usePremiumGate.ts +0 -116
  25. package/src/presentation/hooks/usePremiumWithCredits.md +0 -92
  26. package/src/presentation/hooks/usePremiumWithCredits.ts +0 -48
  27. package/src/presentation/hooks/useSubscription.md +0 -94
  28. package/src/presentation/hooks/useSubscription.ts +0 -119
  29. package/src/presentation/hooks/useSubscriptionDetails.md +0 -93
  30. package/src/presentation/hooks/useSubscriptionDetails.ts +0 -85
  31. package/src/presentation/hooks/useSubscriptionGate.md +0 -84
  32. package/src/presentation/hooks/useSubscriptionGate.ts +0 -67
  33. package/src/presentation/hooks/useSubscriptionStatus.md +0 -94
  34. package/src/presentation/hooks/useSubscriptionStatus.ts +0 -64
  35. package/src/presentation/hooks/useTrialEligibility.ts +0 -66
  36. package/src/presentation/hooks/useUserTier.md +0 -91
  37. package/src/presentation/hooks/useUserTier.ts +0 -78
  38. package/src/presentation/hooks/useUserTierWithRepository.md +0 -92
  39. package/src/presentation/hooks/useUserTierWithRepository.ts +0 -151
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-subscription",
3
- "version": "2.26.14",
3
+ "version": "2.26.15",
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",
@@ -16,6 +16,14 @@ import { usePurchaseLoadingStore, selectIsPurchasing } from "../../../presentati
16
16
 
17
17
  declare const __DEV__: boolean;
18
18
 
19
+ /** Trial eligibility info per product */
20
+ export interface TrialEligibilityInfo {
21
+ /** Whether eligible for trial */
22
+ eligible: boolean;
23
+ /** Trial duration in days */
24
+ durationDays?: number;
25
+ }
26
+
19
27
  export interface PaywallModalProps {
20
28
  visible: boolean;
21
29
  onClose: () => void;
@@ -30,10 +38,12 @@ export interface PaywallModalProps {
30
38
  heroImage?: ImageSourcePropType;
31
39
  onPurchase?: (pkg: PurchasesPackage) => Promise<void | boolean>;
32
40
  onRestore?: () => Promise<void | boolean>;
41
+ /** Trial eligibility map per product ID */
42
+ trialEligibility?: Record<string, TrialEligibilityInfo>;
33
43
  }
34
44
 
35
45
  export const PaywallModal: React.FC<PaywallModalProps> = React.memo((props) => {
36
- const { visible, onClose, translations, packages = [], features = [], isLoading = false, legalUrls = {}, bestValueIdentifier, creditAmounts, creditsLabel, heroImage, onPurchase, onRestore } = props;
46
+ const { visible, onClose, translations, packages = [], features = [], isLoading = false, legalUrls = {}, bestValueIdentifier, creditAmounts, creditsLabel, heroImage, onPurchase, onRestore, trialEligibility = {} } = props;
37
47
  const tokens = useAppDesignTokens();
38
48
  const [selectedPlanId, setSelectedPlanId] = useState<string | null>(null);
39
49
  const [isLocalProcessing, setIsLocalProcessing] = useState(false);
@@ -114,7 +124,11 @@ export const PaywallModal: React.FC<PaywallModalProps> = React.memo((props) => {
114
124
 
115
125
  <View style={styles.header}>
116
126
  <AtomicText type="headlineMedium" style={[styles.title, { color: tokens.colors.textPrimary }]}>{translations.title}</AtomicText>
117
- {translations.subtitle && <AtomicText type="bodyMedium" style={[styles.subtitle, { color: tokens.colors.textSecondary }]}>{translations.subtitle}</AtomicText>}
127
+ {(translations.trialSubtitle || translations.subtitle) && (
128
+ <AtomicText type="bodyMedium" style={[styles.subtitle, { color: tokens.colors.textSecondary }]}>
129
+ {translations.trialSubtitle ?? translations.subtitle}
130
+ </AtomicText>
131
+ )}
118
132
  </View>
119
133
 
120
134
  <PaywallFeatures features={features} />
@@ -123,17 +137,26 @@ export const PaywallModal: React.FC<PaywallModalProps> = React.memo((props) => {
123
137
  <View style={styles.loading}><AtomicSpinner size="lg" color="primary" text={translations.loadingText} /></View>
124
138
  ) : (
125
139
  <View style={styles.plans}>
126
- {packages.map((pkg) => (
127
- <PlanCard
128
- key={pkg.product.identifier}
129
- pkg={pkg}
130
- isSelected={selectedPlanId === pkg.product.identifier}
131
- onSelect={() => setSelectedPlanId(pkg.product.identifier)}
132
- badge={pkg.product.identifier === bestValueIdentifier ? translations.bestValueBadgeText : undefined}
133
- creditAmount={creditAmounts?.[pkg.product.identifier]}
134
- creditsLabel={creditsLabel}
135
- />
136
- ))}
140
+ {packages.map((pkg) => {
141
+ const productId = pkg.product.identifier;
142
+ const eligibility = trialEligibility[productId];
143
+ const hasFreeTrial = eligibility?.eligible ?? false;
144
+
145
+ return (
146
+ <PlanCard
147
+ key={productId}
148
+ pkg={pkg}
149
+ isSelected={selectedPlanId === productId}
150
+ onSelect={() => setSelectedPlanId(productId)}
151
+ badge={productId === bestValueIdentifier ? translations.bestValueBadgeText : undefined}
152
+ creditAmount={creditAmounts?.[productId]}
153
+ creditsLabel={creditsLabel}
154
+ hasFreeTrial={hasFreeTrial}
155
+ trialDurationDays={eligibility?.durationDays}
156
+ trialBadgeText={hasFreeTrial ? translations.trialBadgeText : undefined}
157
+ />
158
+ );
159
+ })}
137
160
  </View>
138
161
  )}
139
162
 
@@ -17,14 +17,23 @@ interface PlanCardProps {
17
17
  badge?: string;
18
18
  creditAmount?: number;
19
19
  creditsLabel?: string;
20
+ /** Whether this plan has a free trial */
21
+ hasFreeTrial?: boolean;
22
+ /** Trial duration in days */
23
+ trialDurationDays?: number;
24
+ /** Trial badge text (e.g., "7 days free") */
25
+ trialBadgeText?: string;
20
26
  }
21
27
 
22
28
  export const PlanCard: React.FC<PlanCardProps> = React.memo(
23
- ({ pkg, isSelected, onSelect, badge, creditAmount, creditsLabel }) => {
29
+ ({ pkg, isSelected, onSelect, badge, creditAmount, creditsLabel, hasFreeTrial, trialBadgeText }) => {
24
30
  const tokens = useAppDesignTokens();
25
31
  const title = pkg.product.title;
26
32
  const price = formatPrice(pkg.product.price, pkg.product.currencyCode);
27
33
 
34
+ // Determine which badge to show (trial badge takes priority if eligible)
35
+ const displayBadge = hasFreeTrial && trialBadgeText ? trialBadgeText : badge;
36
+
28
37
  return (
29
38
  <TouchableOpacity onPress={onSelect} activeOpacity={0.7} style={styles.touchable}>
30
39
  <View
@@ -37,9 +46,13 @@ export const PlanCard: React.FC<PlanCardProps> = React.memo(
37
46
  },
38
47
  ]}
39
48
  >
40
- {badge && (
49
+ {displayBadge && (
41
50
  <View style={styles.badgeContainer}>
42
- <AtomicBadge text={badge} variant="primary" size="sm" />
51
+ <AtomicBadge
52
+ text={displayBadge}
53
+ variant={hasFreeTrial ? "success" : "primary"}
54
+ size="sm"
55
+ />
43
56
  </View>
44
57
  )}
45
58
 
@@ -41,6 +41,10 @@ export interface PaywallTranslations {
41
41
  privacyText?: string;
42
42
  termsOfServiceText?: string;
43
43
  bestValueBadgeText?: string;
44
+ /** Trial-related translations */
45
+ trialBadgeText?: string;
46
+ /** Trial subtitle (e.g., "Try free for 7 days, then $X/year") */
47
+ trialSubtitle?: string;
44
48
  }
45
49
 
46
50
  export interface PaywallLegalUrls {
@@ -13,6 +13,10 @@ interface PaywallTranslationKeys {
13
13
  processingText: string;
14
14
  privacyText: string;
15
15
  termsOfServiceText: string;
16
+ /** Trial badge text key */
17
+ trialBadgeText?: string;
18
+ /** Trial subtitle key */
19
+ trialSubtitle?: string;
16
20
  }
17
21
 
18
22
  interface UsePaywallTranslationsParams {
@@ -38,6 +42,8 @@ const DEFAULT_KEYS: PaywallTranslationKeys = {
38
42
  processingText: "paywall.processing",
39
43
  privacyText: "auth.privacyPolicy",
40
44
  termsOfServiceText: "auth.termsOfService",
45
+ trialBadgeText: "paywall.trial.badge",
46
+ trialSubtitle: "paywall.trial.subtitle",
41
47
  };
42
48
 
43
49
  export const usePaywallTranslations = ({
@@ -62,6 +68,8 @@ export const usePaywallTranslations = ({
62
68
  processingText: t(mergedKeys.processingText),
63
69
  privacyText: t(mergedKeys.privacyText),
64
70
  termsOfServiceText: t(mergedKeys.termsOfServiceText),
71
+ trialBadgeText: mergedKeys.trialBadgeText ? t(mergedKeys.trialBadgeText) : undefined,
72
+ trialSubtitle: mergedKeys.trialSubtitle ? t(mergedKeys.trialSubtitle) : undefined,
65
73
  }),
66
74
  [t, mergedKeys],
67
75
  );
@@ -1,25 +1,10 @@
1
- export * from "./useAuthAwarePurchase";
2
- export * from "./useAuthGate";
3
1
  export * from "./useAuthSubscriptionSync";
4
2
  export * from "./useSavedPurchaseAutoExecution";
5
- export * from "./useCreditChecker";
6
3
  export * from "./useCredits";
7
- export * from "./useCreditsGate";
8
4
  export * from "./useDeductCredit";
9
- export * from "./useInitializeCredits";
10
- export * from "./useDevTestCallbacks";
11
5
  export * from "./useFeatureGate";
12
6
  export * from "./usePaywallVisibility";
13
7
  export * from "./usePremium";
14
- export * from "./usePremiumGate";
15
- export * from "./usePremiumWithCredits";
16
- export * from "./useSubscription";
17
- export * from "./useSubscriptionDetails";
18
- export * from "./useSubscriptionGate";
19
8
  export * from "./useSubscriptionSettingsConfig";
20
- export * from "./useSubscriptionStatus";
21
- export * from "./useUserTier";
22
- export * from "./useUserTierWithRepository";
23
- export * from "./useTrialEligibility";
24
9
  export * from "./feedback/usePaywallFeedback";
25
10
  export * from "./feedback/useFeedbackSubmit";
@@ -1,13 +1,10 @@
1
1
  /**
2
2
  * useFeatureGate Hook
3
- * Combines auth, subscription, and credits gates into a unified feature gate.
3
+ * Unified feature gate: Auth Subscription Credits
4
4
  * Uses ref pattern to avoid stale closure issues.
5
5
  */
6
6
 
7
7
  import { useCallback, useRef, useEffect } from "react";
8
- import { useAuthGate } from "./useAuthGate";
9
- import { useSubscriptionGate } from "./useSubscriptionGate";
10
- import { useCreditsGate } from "./useCreditsGate";
11
8
 
12
9
  declare const __DEV__: boolean;
13
10
 
@@ -29,9 +26,7 @@ export interface UseFeatureGateResult {
29
26
  readonly canAccess: boolean;
30
27
  }
31
28
 
32
- export function useFeatureGate(
33
- params: UseFeatureGateParams
34
- ): UseFeatureGateResult {
29
+ export function useFeatureGate(params: UseFeatureGateParams): UseFeatureGateResult {
35
30
  const {
36
31
  isAuthenticated,
37
32
  onShowAuthModal,
@@ -79,22 +74,6 @@ export function useFeatureGate(
79
74
  prevCreditBalanceRef.current = creditBalance;
80
75
  }, [creditBalance]);
81
76
 
82
- const authGate = useAuthGate({
83
- isAuthenticated,
84
- onAuthRequired: onShowAuthModal,
85
- });
86
-
87
- const subscriptionGate = useSubscriptionGate({
88
- hasSubscription,
89
- onSubscriptionRequired: () => onShowPaywall(requiredCredits),
90
- });
91
-
92
- const creditsGate = useCreditsGate({
93
- creditBalance,
94
- requiredCredits,
95
- onCreditsRequired: onShowPaywall,
96
- });
97
-
98
77
  const requireFeature = useCallback(
99
78
  (action: () => void | Promise<void>) => {
100
79
  if (typeof __DEV__ !== "undefined" && __DEV__) {
@@ -106,7 +85,7 @@ export function useFeatureGate(
106
85
  });
107
86
  }
108
87
 
109
- if (!authGate.requireAuth(() => {})) {
88
+ if (!isAuthenticated) {
110
89
  const postAuthAction = () => {
111
90
  if (hasSubscriptionRef.current) {
112
91
  action();
@@ -132,30 +111,30 @@ export function useFeatureGate(
132
111
  return;
133
112
  }
134
113
 
135
- if (!creditsGate.requireCredits()) {
114
+ const currentBalance = creditBalanceRef.current;
115
+ if (currentBalance < requiredCredits) {
116
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
117
+ console.log("[useFeatureGate] No credits, showing paywall");
118
+ }
136
119
  pendingActionRef.current = action;
137
120
  isWaitingForPurchaseRef.current = true;
121
+ onShowPaywall(requiredCredits);
138
122
  return;
139
123
  }
140
124
 
141
125
  action();
142
126
  },
143
- [authGate, creditsGate, hasSubscription, requiredCredits, isAuthenticated, onShowAuthModal]
127
+ [isAuthenticated, hasSubscription, requiredCredits, onShowAuthModal, onShowPaywall]
144
128
  );
145
129
 
146
130
  const hasCredits = creditBalance >= requiredCredits;
147
131
 
148
132
  return {
149
133
  requireFeature,
150
- isAuthenticated: authGate.isAuthenticated,
151
- hasSubscription: subscriptionGate.hasSubscription,
134
+ isAuthenticated,
135
+ hasSubscription,
152
136
  hasCredits,
153
137
  creditBalance,
154
138
  canAccess: isAuthenticated && (hasSubscription || hasCredits),
155
139
  };
156
140
  }
157
-
158
- export { useAuthGate, useSubscriptionGate, useCreditsGate };
159
- export type { UseAuthGateParams, UseAuthGateResult } from "./useAuthGate";
160
- export type { UseSubscriptionGateParams, UseSubscriptionGateResult } from "./useSubscriptionGate";
161
- export type { UseCreditsGateParams, UseCreditsGateResult } from "./useCreditsGate";
@@ -75,3 +75,35 @@ export function getErrorMessage(error: unknown, fallback: string): string {
75
75
  }
76
76
  return fallback;
77
77
  }
78
+
79
+ /**
80
+ * Trial Eligibility Types
81
+ * For RevenueCat introductory offer (free trial) support
82
+ */
83
+
84
+ /** Trial info for a subscription product */
85
+ export interface TrialInfo {
86
+ /** Whether user is eligible for trial */
87
+ eligible: boolean;
88
+ /** Trial duration in days */
89
+ durationDays: number;
90
+ /** Product identifier */
91
+ productId: string;
92
+ }
93
+
94
+ /** Configuration for trial display */
95
+ export interface TrialDisplayConfig {
96
+ /** Product IDs that have trial offers */
97
+ trialProductIds: string[];
98
+ /** Default trial duration in days */
99
+ defaultTrialDays: number;
100
+ /** Whether to show trial badge */
101
+ showTrialBadge: boolean;
102
+ }
103
+
104
+ /** Default trial configuration */
105
+ export const DEFAULT_TRIAL_CONFIG: TrialDisplayConfig = {
106
+ trialProductIds: [],
107
+ defaultTrialDays: 7,
108
+ showTrialBadge: true,
109
+ };
@@ -10,3 +10,4 @@ export * from "./presentation/hooks/useRevenueCat";
10
10
  export * from "./presentation/hooks/useCustomerInfo";
11
11
  export * from "./presentation/hooks/usePaywallFlow";
12
12
  export * from "./presentation/hooks/useSubscriptionQueries";
13
+ export * from "./presentation/hooks/useRevenueCatTrialEligibility";
@@ -0,0 +1,179 @@
1
+ /**
2
+ * useRevenueCatTrialEligibility Hook
3
+ * Checks if user is eligible for introductory offers via RevenueCat
4
+ * Uses Apple's native mechanism for trial eligibility
5
+ */
6
+
7
+ import { useState, useEffect, useCallback, useRef } from "react";
8
+ import Purchases, {
9
+ type IntroEligibility,
10
+ INTRO_ELIGIBILITY_STATUS,
11
+ } from "react-native-purchases";
12
+ import { getRevenueCatService } from "../../infrastructure/services/RevenueCatService";
13
+
14
+ declare const __DEV__: boolean;
15
+
16
+ /** Trial eligibility info for a single product */
17
+ export interface ProductTrialEligibility {
18
+ /** Product identifier */
19
+ productId: string;
20
+ /** Whether eligible for introductory offer (free trial) */
21
+ eligible: boolean;
22
+ /** Trial duration in days (if available from product) */
23
+ trialDurationDays?: number;
24
+ }
25
+
26
+ /** Map of product ID to eligibility */
27
+ export type TrialEligibilityMap = Record<string, ProductTrialEligibility>;
28
+
29
+ export interface UseRevenueCatTrialEligibilityResult {
30
+ /** Map of product IDs to their trial eligibility */
31
+ eligibilityMap: TrialEligibilityMap;
32
+ /** Whether eligibility check is in progress */
33
+ isLoading: boolean;
34
+ /** Whether any product has an eligible trial */
35
+ hasEligibleTrial: boolean;
36
+ /** Check eligibility for specific product IDs */
37
+ checkEligibility: (productIds: string[]) => Promise<void>;
38
+ /** Get eligibility for a specific product */
39
+ getProductEligibility: (productId: string) => ProductTrialEligibility | null;
40
+ }
41
+
42
+ /** Cache duration in milliseconds (5 minutes) */
43
+ const CACHE_DURATION_MS = 5 * 60 * 1000;
44
+
45
+ /** Cached eligibility result */
46
+ interface CachedEligibility {
47
+ data: TrialEligibilityMap;
48
+ timestamp: number;
49
+ }
50
+
51
+ let eligibilityCache: CachedEligibility | null = null;
52
+
53
+ /**
54
+ * Hook to check trial eligibility via RevenueCat
55
+ * Uses Apple's introductory offer eligibility system
56
+ */
57
+ export function useRevenueCatTrialEligibility(): UseRevenueCatTrialEligibilityResult {
58
+ const [eligibilityMap, setEligibilityMap] = useState<TrialEligibilityMap>({});
59
+ const [isLoading, setIsLoading] = useState(false);
60
+ const isMountedRef = useRef(true);
61
+
62
+ useEffect(() => {
63
+ isMountedRef.current = true;
64
+ return () => {
65
+ isMountedRef.current = false;
66
+ };
67
+ }, []);
68
+
69
+ const checkEligibility = useCallback(async (productIds: string[]) => {
70
+ if (productIds.length === 0) {
71
+ return;
72
+ }
73
+
74
+ // Check cache validity
75
+ if (
76
+ eligibilityCache &&
77
+ Date.now() - eligibilityCache.timestamp < CACHE_DURATION_MS
78
+ ) {
79
+ const allCached = productIds.every(
80
+ (id) => eligibilityCache?.data[id] !== undefined
81
+ );
82
+ if (allCached && isMountedRef.current) {
83
+ setEligibilityMap(eligibilityCache.data);
84
+ return;
85
+ }
86
+ }
87
+
88
+ const service = getRevenueCatService();
89
+ if (!service || !service.isInitialized()) {
90
+ if (__DEV__) {
91
+ console.log("[TrialEligibility] RevenueCat not initialized");
92
+ }
93
+ return;
94
+ }
95
+
96
+ setIsLoading(true);
97
+
98
+ try {
99
+ const eligibilities: Record<string, IntroEligibility> =
100
+ await Purchases.checkTrialOrIntroDiscountEligibility(productIds);
101
+
102
+ const newMap: TrialEligibilityMap = {};
103
+
104
+ for (const productId of productIds) {
105
+ const eligibility = eligibilities[productId];
106
+ const isEligible =
107
+ eligibility?.status === INTRO_ELIGIBILITY_STATUS.INTRO_ELIGIBILITY_STATUS_ELIGIBLE;
108
+
109
+ newMap[productId] = {
110
+ productId,
111
+ eligible: isEligible,
112
+ trialDurationDays: 7, // Default to 7 days as configured in App Store Connect
113
+ };
114
+
115
+ if (__DEV__) {
116
+ console.log(
117
+ `[TrialEligibility] ${productId}: ${isEligible ? "ELIGIBLE" : "NOT_ELIGIBLE"}`
118
+ );
119
+ }
120
+ }
121
+
122
+ // Update cache
123
+ eligibilityCache = {
124
+ data: { ...eligibilityCache?.data, ...newMap },
125
+ timestamp: Date.now(),
126
+ };
127
+
128
+ if (isMountedRef.current) {
129
+ setEligibilityMap((prev) => ({ ...prev, ...newMap }));
130
+ }
131
+ } catch (error) {
132
+ if (__DEV__) {
133
+ console.log("[TrialEligibility] Error checking eligibility:", error);
134
+ }
135
+ // On error, default to eligible (better UX)
136
+ const fallbackMap: TrialEligibilityMap = {};
137
+ for (const productId of productIds) {
138
+ fallbackMap[productId] = {
139
+ productId,
140
+ eligible: true,
141
+ trialDurationDays: 7,
142
+ };
143
+ }
144
+ if (isMountedRef.current) {
145
+ setEligibilityMap((prev) => ({ ...prev, ...fallbackMap }));
146
+ }
147
+ } finally {
148
+ if (isMountedRef.current) {
149
+ setIsLoading(false);
150
+ }
151
+ }
152
+ }, []);
153
+
154
+ const getProductEligibility = useCallback(
155
+ (productId: string): ProductTrialEligibility | null => {
156
+ return eligibilityMap[productId] ?? null;
157
+ },
158
+ [eligibilityMap]
159
+ );
160
+
161
+ const hasEligibleTrial = Object.values(eligibilityMap).some(
162
+ (e) => e.eligible
163
+ );
164
+
165
+ return {
166
+ eligibilityMap,
167
+ isLoading,
168
+ hasEligibleTrial,
169
+ checkEligibility,
170
+ getProductEligibility,
171
+ };
172
+ }
173
+
174
+ /**
175
+ * Clear eligibility cache (useful for testing)
176
+ */
177
+ export function clearTrialEligibilityCache(): void {
178
+ eligibilityCache = null;
179
+ }
@@ -1,92 +0,0 @@
1
- # useAuthAwarePurchase Hook
2
-
3
- Security-focused purchase hook that requires authentication before any transaction.
4
-
5
- ## Location
6
-
7
- **Import Path**: `@umituz/react-native-subscription`
8
-
9
- **File**: `src/presentation/hooks/useAuthAwarePurchase.ts`
10
-
11
- **Type**: Hook
12
-
13
- ## Strategy
14
-
15
- ### Auth-Gated Purchase Flow
16
-
17
- 1. **Auth Provider Validation**: Verify auth provider is configured at app startup
18
- 2. **Authentication Check**: Block purchases for unauthenticated users
19
- 3. **Auth Flow Trigger**: Show auth modal when guest attempts purchase
20
- 4. **Purchase Blocking**: Prevent all transactions without valid authentication
21
- 5. **Post-Auth Purchase**: Allow purchase after user completes authentication
22
- 6. **Security Enforcement**: Server-side validation required for final verification
23
-
24
- ### Integration Points
25
-
26
- - **Auth Provider Configuration**: Must be configured once at app initialization
27
- - **Auth Context**: User authentication state
28
- - **Paywall Domain**: For subscription upgrade flow
29
- - **Auth UI**: For sign-in/sign-up flows
30
- - **RevenueCat**: For purchase transactions
31
-
32
- ## Restrictions
33
-
34
- ### REQUIRED
35
-
36
- - **Auth Provider Configuration**: MUST call `configureAuthProvider()` once at app startup
37
- - **isAuthenticated Function**: MUST provide function to check auth status
38
- - **showAuthModal Function**: MUST provide function to show auth UI
39
- - **Error Handling**: MUST handle purchase failures appropriately
40
-
41
- ### PROHIBITED
42
-
43
- - **NEVER** use without configuring auth provider first
44
- - **NEVER** bypass auth checks for convenience
45
- - **NEVER** allow anonymous/guest purchases
46
- - **DO NOT** call handlePurchase/handleRestore without auth provider setup
47
-
48
- ### CRITICAL SAFETY
49
-
50
- - **ALWAYS** configure auth provider at app initialization
51
- - **NEVER** allow purchases for anonymous users
52
- - **MUST** implement proper auth flow with pending purchase preservation
53
- - **ALWAYS** verify auth status in production
54
-
55
- ## AI Agent Guidelines
56
-
57
- ### When Implementing Auth-Gated Purchases
58
-
59
- 1. **Always** configure auth provider at app startup
60
- 2. **Always** implement isAuthenticated function
61
- 3. **Always** implement showAuthModal function
62
- 4. **Never** bypass auth checks
63
- 5. **Always** test purchase flow with authenticated and unauthenticated users
64
-
65
- ### Integration Checklist
66
-
67
- - [ ] Import from correct path: `@umituz/react-native-subscription`
68
- - [ ] Call `configureAuthProvider()` once at app startup
69
- - [ ] Provide `isAuthenticated()` function
70
- - [ ] Provide `showAuthModal()` function
71
- - [ ] Test purchase flow with authenticated user
72
- - [ ] Test purchase flow with unauthenticated user
73
- - [ ] Verify auth modal appears for guests
74
- - [ ] Verify purchase proceeds after authentication
75
- - [ ] Check development logs for auth verification
76
- - [ ] Verify purchases are blocked without auth provider
77
-
78
- ### Common Patterns
79
-
80
- 1. **App-Level Config**: Configure once in root App component
81
- 2. **Pending Purchase**: Store package for post-auth completion
82
- 3. **Auth Integration**: Use with Firebase, Auth0, or custom auth
83
- 4. **Error Handling**: Handle auth failures and purchase failures
84
- 5. **Development Testing**: Use dev logs to verify auth checks
85
-
86
- ## Related Documentation
87
-
88
- - **usePremium**: For purchase and restore operations
89
- - **usePaywallOperations**: For complete paywall purchase handling
90
- - **useAuthGate**: For authentication gating
91
- - **useAuthSubscriptionSync**: For syncing auth with subscription
92
- - **Security Best Practices**: `src/docs/SECURITY.md`