@umituz/react-native-subscription 2.41.13 → 2.42.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.41.13",
3
+ "version": "2.42.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",
@@ -33,8 +33,14 @@ import { hasItems } from "../../../shared/utils/arrayUtils";
33
33
  export const PaywallScreen: React.FC<PaywallScreenProps> = React.memo((props) => {
34
34
  const navigation = useNavigation();
35
35
 
36
- // Get purchase functions from usePremium as fallback
37
- const { purchasePackage: premiumPurchase, restorePurchase: premiumRestore } = usePremium();
36
+ if (__DEV__) {
37
+ console.log('[PaywallScreen] 📱 Rendering PaywallScreen', {
38
+ hasPackages: !!props.packages?.length,
39
+ packagesCount: props.packages?.length || 0,
40
+ });
41
+ }
42
+
43
+ const { purchasePackage, restorePurchase } = usePremium();
38
44
 
39
45
  const {
40
46
  onClose,
@@ -46,8 +52,6 @@ export const PaywallScreen: React.FC<PaywallScreenProps> = React.memo((props) =>
46
52
  creditAmounts,
47
53
  creditsLabel,
48
54
  heroImage,
49
- onPurchase,
50
- onRestore,
51
55
  onPurchaseSuccess,
52
56
  onPurchaseError,
53
57
  onAuthRequired,
@@ -58,23 +62,8 @@ export const PaywallScreen: React.FC<PaywallScreenProps> = React.memo((props) =>
58
62
  const tokens = useAppDesignTokens();
59
63
  const insets = useSafeAreaInsets();
60
64
 
61
- // Use provided purchase functions or fall back to usePremium
62
- const handlePurchasePackage = useCallback(async (pkg: any) => {
63
- if (onPurchase) {
64
- return await onPurchase(pkg);
65
- }
66
- return await premiumPurchase(pkg);
67
- }, [onPurchase, premiumPurchase]);
68
-
69
- const handleRestorePackages = useCallback(async () => {
70
- if (onRestore) {
71
- return await onRestore();
72
- }
73
- return await premiumRestore();
74
- }, [onRestore, premiumRestore]);
75
-
76
- // Default close handler using navigation
77
65
  const handleClose = useCallback(() => {
66
+ if (__DEV__) console.log('[PaywallScreen] 🔙 Closing paywall');
78
67
  if (onClose) {
79
68
  onClose();
80
69
  } else if (navigation.canGoBack()) {
@@ -91,8 +80,8 @@ export const PaywallScreen: React.FC<PaywallScreenProps> = React.memo((props) =>
91
80
  resetState
92
81
  } = usePaywallActions({
93
82
  packages,
94
- onPurchase: handlePurchasePackage,
95
- onRestore: handleRestorePackages,
83
+ purchasePackage,
84
+ restorePurchase,
96
85
  source,
97
86
  onPurchaseSuccess,
98
87
  onPurchaseError,
@@ -100,12 +89,12 @@ export const PaywallScreen: React.FC<PaywallScreenProps> = React.memo((props) =>
100
89
  onClose: handleClose
101
90
  });
102
91
 
103
- // Reset state when screen is closed to avoid lockups
104
92
  useEffect(() => {
105
93
  return () => {
94
+ if (__DEV__) console.log('[PaywallScreen] 🧹 Cleanup: resetting state');
106
95
  resetState();
107
96
  };
108
- }, [resetState, handlePurchasePackage, handleRestorePackages]);
97
+ }, [resetState]);
109
98
 
110
99
  // Auto-select first package
111
100
  useEffect(() => {
@@ -228,7 +217,7 @@ export const PaywallScreen: React.FC<PaywallScreenProps> = React.memo((props) =>
228
217
 
229
218
 
230
219
  // Performance Optimization: getItemLayout for FlatList
231
- const getItemLayout = useCallback((_data: any, index: number) => {
220
+ const getItemLayout = useCallback((_data: ArrayLike<PaywallListItem> | null, index: number) => {
232
221
  return calculatePaywallItemLayout(flatData, index);
233
222
  }, [flatData]);
234
223
 
@@ -322,7 +311,7 @@ export const PaywallScreen: React.FC<PaywallScreenProps> = React.memo((props) =>
322
311
  translations={translations}
323
312
  legalUrls={legalUrls}
324
313
  isProcessing={isProcessing}
325
- onRestore={onRestore ? handleRestore : undefined}
314
+ onRestore={handleRestore}
326
315
  onLegalClick={handleLegalUrl}
327
316
  />
328
317
  </View>
@@ -13,8 +13,7 @@ export interface PaywallScreenProps {
13
13
  creditAmounts?: Record<string, number>;
14
14
  creditsLabel?: string;
15
15
  heroImage?: ImageSourcePropType;
16
- onPurchase?: (pkg: PurchasesPackage) => Promise<void | boolean>;
17
- onRestore?: () => Promise<void | boolean>;
16
+ // Purchase/restore handlers removed - always use usePremium hook internally
18
17
  onPurchaseSuccess?: () => void;
19
18
  onPurchaseError?: (error: Error | string) => void;
20
19
  onAuthRequired?: () => void;
@@ -7,8 +7,8 @@ import { useCredits } from "../../credits/presentation/useCredits";
7
7
 
8
8
  interface UsePaywallActionsParams {
9
9
  packages?: PurchasesPackage[];
10
- onPurchase?: (pkg: PurchasesPackage) => Promise<void | boolean>;
11
- onRestore?: () => Promise<void | boolean>;
10
+ purchasePackage: (pkg: PurchasesPackage) => Promise<boolean>;
11
+ restorePurchase: () => Promise<boolean>;
12
12
  source?: PurchaseSource;
13
13
  onPurchaseSuccess?: () => void;
14
14
  onPurchaseError?: (error: Error | string) => void;
@@ -18,8 +18,8 @@ interface UsePaywallActionsParams {
18
18
 
19
19
  export function usePaywallActions({
20
20
  packages = [],
21
- onPurchase,
22
- onRestore,
21
+ purchasePackage,
22
+ restorePurchase,
23
23
  onPurchaseSuccess,
24
24
  onPurchaseError,
25
25
  onAuthRequired,
@@ -28,33 +28,26 @@ export function usePaywallActions({
28
28
  const [selectedPlanId, setSelectedPlanId] = useState<string | null>(null);
29
29
  const [isLocalProcessing, setIsLocalProcessing] = useState(false);
30
30
 
31
- // Use optimized selector for global purchasing state
32
31
  const isGlobalPurchasing = usePurchaseLoadingStore(selectIsPurchasing);
33
-
34
- // Get premium and credits status for fallback success checks
35
32
  const { refetch: refetchStatus } = useSubscriptionStatus();
36
33
  const { refetch: refetchCredits } = useCredits();
37
-
38
- // Combine processing states
34
+
39
35
  const isProcessing = isLocalProcessing || isGlobalPurchasing;
40
-
41
- // Use ref for isProcessing to keep callbacks stable without re-creating them
42
36
  const isProcessingRef = useRef(isProcessing);
43
37
  isProcessingRef.current = isProcessing;
44
38
 
45
39
  const { startPurchase, endPurchase } = usePurchaseLoadingStore();
46
40
 
47
- const onPurchaseRef = useRef(onPurchase);
48
- const onRestoreRef = useRef(onRestore);
41
+ const purchasePackageRef = useRef(purchasePackage);
42
+ const restorePurchaseRef = useRef(restorePurchase);
49
43
  const onPurchaseSuccessRef = useRef(onPurchaseSuccess);
50
44
  const onPurchaseErrorRef = useRef(onPurchaseError);
51
45
  const onAuthRequiredRef = useRef(onAuthRequired);
52
46
  const onCloseRef = useRef(onClose);
53
47
  const packagesRef = useRef(packages);
54
48
 
55
- // Update refs in render body — always in sync
56
- onPurchaseRef.current = onPurchase;
57
- onRestoreRef.current = onRestore;
49
+ purchasePackageRef.current = purchasePackage;
50
+ restorePurchaseRef.current = restorePurchase;
58
51
  onPurchaseSuccessRef.current = onPurchaseSuccess;
59
52
  onPurchaseErrorRef.current = onPurchaseError;
60
53
  onAuthRequiredRef.current = onAuthRequired;
@@ -62,29 +55,20 @@ export function usePaywallActions({
62
55
  packagesRef.current = packages;
63
56
 
64
57
  const handlePurchase = useCallback(async () => {
65
- // Access current state via refs to keep callback stable
66
58
  const currentSelectedId = selectedPlanId;
67
59
  if (!currentSelectedId) return;
68
60
 
69
- if (!onPurchaseRef.current) {
70
- onPurchaseErrorRef.current?.(new Error("Purchase handler not configured"));
71
- return;
72
- }
73
-
74
61
  if (isProcessingRef.current) return;
75
62
 
76
63
  const pkg = packagesRef.current.find((p) => p.product.identifier === currentSelectedId);
77
-
78
64
  if (!pkg) {
79
65
  onPurchaseErrorRef.current?.(new Error(`Package not found: ${currentSelectedId}`));
80
66
  return;
81
67
  }
82
68
 
83
69
  if (__DEV__) {
84
- console.log('[usePaywallActions] Starting purchase', {
70
+ console.log('[usePaywallActions] 🛒 Starting purchase', {
85
71
  productId: pkg.product.identifier,
86
- hasOnClose: !!onCloseRef.current,
87
- hasOnSuccess: !!onPurchaseSuccessRef.current,
88
72
  });
89
73
  }
90
74
 
@@ -92,87 +76,77 @@ export function usePaywallActions({
92
76
  startPurchase(currentSelectedId, "manual");
93
77
 
94
78
  try {
95
- const success = await onPurchaseRef.current(pkg);
79
+ const success = await purchasePackageRef.current(pkg);
80
+
96
81
  if (__DEV__) {
97
- console.log('[usePaywallActions] Purchase completed', { success });
82
+ console.log('[usePaywallActions] Purchase completed', { success });
98
83
  }
99
84
 
100
- // Check if purchase was successful
101
- // We consider it success if:
102
- // 1. Handler returns true
103
- // 2. Handler returns void (undefined) but user is now premium (fallback for misconfigured handlers)
104
85
  let isActuallySuccessful = success === true;
105
-
86
+
106
87
  if (success === undefined) {
107
88
  if (__DEV__) {
108
- console.log('[usePaywallActions] Handler returned undefined, checking premium status as fallback...');
89
+ console.log('[usePaywallActions] 🔍 Checking premium status as fallback...');
109
90
  }
110
-
111
- // Use Promise.all to fetch both in parallel
91
+
112
92
  const [statusResult, creditsResult] = await Promise.all([
113
93
  refetchStatus(),
114
94
  refetchCredits()
115
95
  ]);
116
-
96
+
117
97
  const isSubscriptionPremium = statusResult.data?.isPremium ?? false;
118
98
  const isCreditsPremium = creditsResult.data?.isPremium ?? false;
119
-
99
+
120
100
  isActuallySuccessful = isSubscriptionPremium || isCreditsPremium;
121
-
101
+
122
102
  if (__DEV__) {
123
- console.log('[usePaywallActions] Fallback check result:', {
124
- isSubscriptionPremium,
125
- isCreditsPremium,
126
- isActuallySuccessful
103
+ console.log('[usePaywallActions] 📊 Fallback check result:', {
104
+ isSubscriptionPremium,
105
+ isCreditsPremium,
106
+ isActuallySuccessful
127
107
  });
128
108
  }
129
109
  }
130
110
 
131
111
  if (isActuallySuccessful) {
132
112
  if (__DEV__) {
133
- console.log('[usePaywallActions] Purchase successful, calling onPurchaseSuccess and onClose');
113
+ console.log('[usePaywallActions] 🎉 Purchase successful, closing paywall');
134
114
  }
135
115
  onPurchaseSuccessRef.current?.();
136
- // Always close paywall on successful purchase
137
116
  onCloseRef.current?.();
138
117
  } else {
139
118
  if (__DEV__) {
140
- console.warn('[usePaywallActions] Purchase did not indicate success, not closing');
119
+ console.warn('[usePaywallActions] ⚠️ Purchase did not indicate success');
141
120
  }
142
121
  }
143
122
  } catch (error) {
123
+ if (__DEV__) {
124
+ console.error('[usePaywallActions] ❌ Purchase error:', error);
125
+ }
144
126
  onPurchaseErrorRef.current?.(error instanceof Error ? error : new Error(String(error)));
145
127
  } finally {
146
128
  setIsLocalProcessing(false);
147
129
  endPurchase(currentSelectedId);
148
130
  }
149
- }, [selectedPlanId, startPurchase, endPurchase, refetchStatus, refetchCredits]); // Only depend on state that must trigger re-creation if changed
131
+ }, [selectedPlanId, startPurchase, endPurchase, refetchStatus, refetchCredits]);
150
132
 
151
133
  const handleRestore = useCallback(async () => {
152
- if (!onRestoreRef.current) {
153
- onPurchaseErrorRef.current?.(new Error("Restore handler not configured"));
154
- return;
155
- }
156
-
157
134
  if (isProcessingRef.current) return;
158
135
 
159
136
  if (__DEV__) {
160
- console.log('[usePaywallActions] Starting restore', {
161
- hasOnClose: !!onCloseRef.current,
162
- hasOnSuccess: !!onPurchaseSuccessRef.current,
163
- });
137
+ console.log('[usePaywallActions] 🔄 Starting restore');
164
138
  }
165
139
 
166
140
  setIsLocalProcessing(true);
167
141
  try {
168
- const success = await onRestoreRef.current();
142
+ const success = await restorePurchaseRef.current();
143
+
169
144
  if (__DEV__) {
170
- console.log('[usePaywallActions] Restore completed', { success });
145
+ console.log('[usePaywallActions] Restore completed', { success });
171
146
  }
172
147
 
173
- // Check success with same fallback as purchase
174
148
  let isActuallySuccessful = success === true;
175
-
149
+
176
150
  if (success === undefined) {
177
151
  const [statusResult, creditsResult] = await Promise.all([
178
152
  refetchStatus(),
@@ -183,23 +157,29 @@ export function usePaywallActions({
183
157
 
184
158
  if (isActuallySuccessful) {
185
159
  if (__DEV__) {
186
- console.log('[usePaywallActions] Restore successful, calling onPurchaseSuccess and onClose');
160
+ console.log('[usePaywallActions] 🎉 Restore successful, closing paywall');
187
161
  }
188
162
  onPurchaseSuccessRef.current?.();
189
163
  onCloseRef.current?.();
190
164
  } else {
191
165
  if (__DEV__) {
192
- console.warn('[usePaywallActions] Restore did not indicate success, not closing');
166
+ console.warn('[usePaywallActions] ⚠️ Restore did not indicate success');
193
167
  }
194
168
  }
195
169
  } catch (error) {
170
+ if (__DEV__) {
171
+ console.error('[usePaywallActions] ❌ Restore error:', error);
172
+ }
196
173
  onPurchaseErrorRef.current?.(error instanceof Error ? error : new Error(String(error)));
197
174
  } finally {
198
175
  setIsLocalProcessing(false);
199
176
  }
200
- }, [refetchStatus, refetchCredits]); // Truly stable callback
177
+ }, [refetchStatus, refetchCredits]);
201
178
 
202
179
  const resetState = useCallback(() => {
180
+ if (__DEV__) {
181
+ console.log('[usePaywallActions] 🧹 Resetting state');
182
+ }
203
183
  setSelectedPlanId(null);
204
184
  setIsLocalProcessing(false);
205
185
  }, []);
@@ -1,15 +1,17 @@
1
- import { useEffect, useCallback, useRef } from "react";
1
+ import { useEffect, useRef } from "react";
2
+ import type { NavigationProp } from "@react-navigation/native";
3
+ import type { ImageSourcePropType } from "react-native";
2
4
  import { usePremium } from "../../subscription/presentation/usePremium";
3
5
  import { useSubscriptionFlowStore } from "../../subscription/presentation/useSubscriptionFlow";
4
6
  import { usePaywallVisibility } from "../../subscription/presentation/usePaywallVisibility";
5
7
  import { PaywallTranslations, PaywallLegalUrls, SubscriptionFeature } from "../entities/types";
6
8
 
7
9
  export interface PaywallOrchestratorOptions {
8
- navigation: any;
10
+ navigation: NavigationProp<any>;
9
11
  translations: PaywallTranslations;
10
12
  features: SubscriptionFeature[];
11
13
  legalUrls: PaywallLegalUrls;
12
- heroImage: any;
14
+ heroImage: ImageSourcePropType;
13
15
  isNavReady?: boolean;
14
16
  isLocalizationReady?: boolean;
15
17
  onAuthRequired?: () => void;
@@ -36,11 +38,12 @@ export function usePaywallOrchestrator({
36
38
  bestValueIdentifier = "yearly",
37
39
  creditsLabel,
38
40
  }: PaywallOrchestratorOptions) {
41
+ // Sadece isPremium ve packages alıyoruz
42
+ // purchasePackage ve restoreRestore fonksiyonlarını ALMIYORUZ
43
+ // Çünkü PaywallScreen zaten usePremium hook'unu doğrudan kullanıyor
39
44
  const {
40
45
  isPremium,
41
- packages,
42
- purchasePackage,
43
- restorePurchase
46
+ packages
44
47
  } = usePremium();
45
48
 
46
49
  // Selectors for stable references and fine-grained updates
@@ -54,7 +57,6 @@ export function usePaywallOrchestrator({
54
57
  const setShowFeedback = useSubscriptionFlowStore((state) => state.setShowFeedback);
55
58
 
56
59
  const { showPaywall, closePaywall } = usePaywallVisibility();
57
- const purchasedRef = useRef(false);
58
60
  const hasNavigatedRef = useRef(false);
59
61
 
60
62
  useEffect(() => {
@@ -74,10 +76,13 @@ export function usePaywallOrchestrator({
74
76
  if (hasNavigatedRef.current) return;
75
77
  hasNavigatedRef.current = true;
76
78
 
77
- if (__DEV__) console.log('[usePaywallOrchestrator] 🚀 Navigating to Paywall', {
78
- source: shouldShowPostOnboarding ? "onboarding" : "manual"
79
+ if (__DEV__) console.log('[usePaywallOrchestrator] 🚀 Navigating to Paywall', {
80
+ source: shouldShowPostOnboarding ? "onboarding" : "manual",
81
+ packagesCount: packages.length
79
82
  });
80
83
 
84
+ // SADECE DATA geçiyoruz - FONKSİYON YOK
85
+ // PaywallScreen kendi usePremium hook'unu kullanacak
81
86
  navigation.navigate("PaywallScreen", {
82
87
  translations,
83
88
  legalUrls,
@@ -87,8 +92,6 @@ export function usePaywallOrchestrator({
87
92
  heroImage,
88
93
  source: shouldShowPostOnboarding ? "onboarding" : "manual",
89
94
  packages,
90
- onPurchase: purchasePackage,
91
- onRestore: restorePurchase,
92
95
  onPurchaseSuccess,
93
96
  onAuthRequired,
94
97
  });
@@ -119,8 +122,6 @@ export function usePaywallOrchestrator({
119
122
  features,
120
123
  heroImage,
121
124
  packages,
122
- purchasePackage,
123
- restorePurchase,
124
125
  markPaywallShown,
125
126
  closePaywall,
126
127
  bestValueIdentifier,
@@ -1,4 +1,6 @@
1
1
  import React, { useState, useEffect } from "react";
2
+ import type { NavigationProp } from "@react-navigation/native";
3
+ import type { ImageSourcePropType } from "react-native";
2
4
  import { SplashScreen, useSplashFlow } from "@umituz/react-native-design-system/molecules";
3
5
  import { OnboardingScreen } from "@umituz/react-native-design-system/onboarding";
4
6
  import { OfflineBanner } from "@umituz/react-native-design-system/offline";
@@ -11,7 +13,7 @@ import { usePaywallFeedbackSubmit } from "../../../../presentation/hooks/feedbac
11
13
 
12
14
  export interface ManagedSubscriptionFlowProps {
13
15
  children: React.ReactNode;
14
- navigation: any;
16
+ navigation: NavigationProp<any>;
15
17
  islocalizationReady: boolean;
16
18
 
17
19
  // Splash Configuration
@@ -40,7 +42,7 @@ export interface ManagedSubscriptionFlowProps {
40
42
  translations: PaywallTranslations;
41
43
  features: SubscriptionFeature[];
42
44
  legalUrls: PaywallLegalUrls;
43
- heroImage: any;
45
+ heroImage: ImageSourcePropType;
44
46
  bestValueIdentifier?: string;
45
47
  creditsLabel?: string;
46
48
  onAuthRequired?: () => void;
@@ -14,7 +14,6 @@ import { UsePremiumResult } from './usePremium.types';
14
14
  const EMPTY_PACKAGES: PurchasesPackage[] = [];
15
15
 
16
16
  export const usePremium = (): UsePremiumResult => {
17
-
18
17
  const { isPremium: subscriptionActive, isLoading: statusLoading } = useSubscriptionStatus();
19
18
  const { credits, isLoading: creditsLoading } = useCredits();
20
19
 
@@ -38,7 +37,10 @@ export const usePremium = (): UsePremiumResult => {
38
37
  try {
39
38
  const result = await purchaseMutation.mutateAsync(pkg);
40
39
  return result.success;
41
- } catch {
40
+ } catch (error) {
41
+ if (__DEV__) {
42
+ console.error('[usePremium] Purchase failed:', error);
43
+ }
42
44
  return false;
43
45
  }
44
46
  },
@@ -49,7 +51,10 @@ export const usePremium = (): UsePremiumResult => {
49
51
  try {
50
52
  const result = await restoreMutation.mutateAsync();
51
53
  return result.success;
52
- } catch {
54
+ } catch (error) {
55
+ if (__DEV__) {
56
+ console.error('[usePremium] Restore failed:', error);
57
+ }
53
58
  return false;
54
59
  }
55
60
  }, [restoreMutation]);