@umituz/react-native-subscription 2.41.7 → 2.41.8
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 +1 -1
- package/src/domains/credits/application/RefundCreditsCommand.ts +2 -1
- package/src/domains/credits/presentation/deduct-credit/useDeductCredit.ts +7 -5
- package/src/domains/paywall/hooks/usePaywallOrchestrator.ts +58 -33
- package/src/domains/subscription/infrastructure/services/listeners/CustomerInfoHandler.ts +12 -4
- package/src/domains/subscription/infrastructure/utils/PremiumStatusSyncer.ts +9 -5
- package/src/domains/subscription/presentation/components/ManagedSubscriptionFlow.tsx +3 -2
- package/src/domains/subscription/presentation/useSubscriptionFlow.ts +1 -19
- package/src/index.ts +81 -14
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-subscription",
|
|
3
|
-
"version": "2.41.
|
|
3
|
+
"version": "2.41.8",
|
|
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",
|
|
@@ -41,7 +41,8 @@ export async function refundCreditsOperation(
|
|
|
41
41
|
|
|
42
42
|
const data = docSnap.data();
|
|
43
43
|
const current = data.credits as number;
|
|
44
|
-
|
|
44
|
+
// If creditLimit is null, use the refund amount as the limit to prevent unlimited credits
|
|
45
|
+
const creditLimit = (data.creditLimit as number) ?? (current + amount);
|
|
45
46
|
const updated = Math.min(current + amount, creditLimit);
|
|
46
47
|
|
|
47
48
|
tx.update(creditsRef, {
|
|
@@ -63,11 +63,13 @@ export const useDeductCredit = ({
|
|
|
63
63
|
}
|
|
64
64
|
return false;
|
|
65
65
|
} catch (error) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
66
|
+
if (__DEV__) {
|
|
67
|
+
console.error('[useDeductCredit] Unexpected error during credit refund', {
|
|
68
|
+
amount,
|
|
69
|
+
userId,
|
|
70
|
+
error: error instanceof Error ? error.message : String(error),
|
|
71
|
+
});
|
|
72
|
+
}
|
|
71
73
|
return false;
|
|
72
74
|
}
|
|
73
75
|
}, [userId, repository, queryClient]);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useEffect, useCallback, useRef } from "react";
|
|
2
2
|
import { usePremium } from "../../subscription/presentation/usePremium";
|
|
3
|
-
import {
|
|
3
|
+
import { useSubscriptionFlowStore } from "../../subscription/presentation/useSubscriptionFlow";
|
|
4
4
|
import { usePaywallVisibility } from "../../subscription/presentation/usePaywallVisibility";
|
|
5
5
|
import { PaywallTranslations, PaywallLegalUrls, SubscriptionFeature } from "../entities/types";
|
|
6
6
|
|
|
@@ -36,34 +36,39 @@ export function usePaywallOrchestrator({
|
|
|
36
36
|
bestValueIdentifier = "yearly",
|
|
37
37
|
creditsLabel,
|
|
38
38
|
}: PaywallOrchestratorOptions) {
|
|
39
|
-
const {
|
|
40
|
-
isPremium,
|
|
41
|
-
packages,
|
|
42
|
-
purchasePackage,
|
|
43
|
-
restorePurchase
|
|
39
|
+
const {
|
|
40
|
+
isPremium,
|
|
41
|
+
packages,
|
|
42
|
+
purchasePackage,
|
|
43
|
+
restorePurchase
|
|
44
44
|
} = usePremium();
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
45
|
+
|
|
46
|
+
// Selectors for stable references and fine-grained updates
|
|
47
|
+
const isOnboardingComplete = useSubscriptionFlowStore((state) => state.isOnboardingComplete);
|
|
48
|
+
const showPostOnboardingPaywall = useSubscriptionFlowStore((state) => state.showPostOnboardingPaywall);
|
|
49
|
+
const paywallShown = useSubscriptionFlowStore((state) => state.paywallShown);
|
|
50
|
+
const isAuthModalOpen = useSubscriptionFlowStore((state) => state.isAuthModalOpen);
|
|
51
|
+
const showFeedback = useSubscriptionFlowStore((state) => state.showFeedback);
|
|
52
|
+
const markPaywallShown = useSubscriptionFlowStore((state) => state.markPaywallShown);
|
|
53
|
+
const closePostOnboardingPaywall = useSubscriptionFlowStore((state) => state.closePostOnboardingPaywall);
|
|
54
|
+
const setShowFeedback = useSubscriptionFlowStore((state) => state.setShowFeedback);
|
|
55
|
+
|
|
53
56
|
const { showPaywall, closePaywall } = usePaywallVisibility();
|
|
54
57
|
const purchasedRef = useRef(false);
|
|
58
|
+
const hasNavigatedRef = useRef(false);
|
|
55
59
|
|
|
56
60
|
const handleClose = useCallback(async () => {
|
|
57
61
|
await closePostOnboardingPaywall();
|
|
58
62
|
closePaywall();
|
|
59
|
-
|
|
63
|
+
|
|
60
64
|
// Trigger feedback if user declined and isn't premium
|
|
61
65
|
if (!isPremium && !purchasedRef.current) {
|
|
62
66
|
setTimeout(() => setShowFeedback(true), 300);
|
|
63
67
|
}
|
|
64
|
-
|
|
68
|
+
|
|
65
69
|
purchasedRef.current = false;
|
|
66
|
-
|
|
70
|
+
hasNavigatedRef.current = false;
|
|
71
|
+
|
|
67
72
|
if (navigation.canGoBack()) {
|
|
68
73
|
navigation.goBack();
|
|
69
74
|
}
|
|
@@ -73,9 +78,9 @@ export function usePaywallOrchestrator({
|
|
|
73
78
|
purchasedRef.current = true;
|
|
74
79
|
await markPaywallShown();
|
|
75
80
|
await closePostOnboardingPaywall();
|
|
76
|
-
|
|
81
|
+
|
|
77
82
|
onPurchaseSuccess?.();
|
|
78
|
-
|
|
83
|
+
|
|
79
84
|
if (navigation.canGoBack()) {
|
|
80
85
|
navigation.goBack();
|
|
81
86
|
}
|
|
@@ -84,16 +89,24 @@ export function usePaywallOrchestrator({
|
|
|
84
89
|
useEffect(() => {
|
|
85
90
|
if (!isNavReady || !isLocalizationReady) return;
|
|
86
91
|
|
|
87
|
-
const shouldShowPostOnboarding =
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
!
|
|
91
|
-
!
|
|
92
|
+
const shouldShowPostOnboarding =
|
|
93
|
+
isOnboardingComplete &&
|
|
94
|
+
showPostOnboardingPaywall &&
|
|
95
|
+
!paywallShown &&
|
|
96
|
+
!isAuthModalOpen &&
|
|
92
97
|
!isPremium;
|
|
93
98
|
|
|
94
|
-
const shouldShowManual = showPaywall && !isPremium && !
|
|
99
|
+
const shouldShowManual = showPaywall && !isPremium && !isAuthModalOpen;
|
|
95
100
|
|
|
96
101
|
if (shouldShowPostOnboarding || shouldShowManual) {
|
|
102
|
+
// Guard against double navigation in same render cycle
|
|
103
|
+
if (hasNavigatedRef.current) return;
|
|
104
|
+
hasNavigatedRef.current = true;
|
|
105
|
+
|
|
106
|
+
if (__DEV__) console.log('[usePaywallOrchestrator] 🚀 Navigating to Paywall', {
|
|
107
|
+
source: shouldShowPostOnboarding ? "onboarding" : "manual"
|
|
108
|
+
});
|
|
109
|
+
|
|
97
110
|
navigation.navigate("PaywallScreen", {
|
|
98
111
|
onClose: handleClose,
|
|
99
112
|
translations,
|
|
@@ -117,14 +130,17 @@ export function usePaywallOrchestrator({
|
|
|
117
130
|
if (showPaywall) {
|
|
118
131
|
closePaywall();
|
|
119
132
|
}
|
|
133
|
+
} else {
|
|
134
|
+
// Reset navigation flag if conditions no longer met
|
|
135
|
+
hasNavigatedRef.current = false;
|
|
120
136
|
}
|
|
121
137
|
}, [
|
|
122
138
|
isNavReady,
|
|
123
139
|
isLocalizationReady,
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
140
|
+
isOnboardingComplete,
|
|
141
|
+
showPostOnboardingPaywall,
|
|
142
|
+
paywallShown,
|
|
143
|
+
isAuthModalOpen,
|
|
128
144
|
isPremium,
|
|
129
145
|
showPaywall,
|
|
130
146
|
navigation,
|
|
@@ -144,12 +160,21 @@ export function usePaywallOrchestrator({
|
|
|
144
160
|
onAuthRequired
|
|
145
161
|
]);
|
|
146
162
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
163
|
+
const completeOnboarding = useSubscriptionFlowStore((state) => state.completeOnboarding);
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
isPremium,
|
|
167
|
+
packages,
|
|
168
|
+
flowState: {
|
|
169
|
+
isOnboardingComplete,
|
|
170
|
+
showPostOnboardingPaywall,
|
|
171
|
+
paywallShown,
|
|
172
|
+
isAuthModalOpen,
|
|
173
|
+
showFeedback
|
|
174
|
+
},
|
|
151
175
|
markPaywallShown,
|
|
152
176
|
closePostOnboardingPaywall,
|
|
177
|
+
completeOnboarding,
|
|
153
178
|
setShowFeedback
|
|
154
179
|
};
|
|
155
180
|
}
|
|
@@ -85,20 +85,28 @@ export async function processCustomerInfo(
|
|
|
85
85
|
);
|
|
86
86
|
|
|
87
87
|
if (renewalResult.isRenewal) {
|
|
88
|
+
if (!renewalResult.productId || !renewalResult.newExpirationDate) {
|
|
89
|
+
console.error('[CustomerInfoHandler] Invalid renewal state: missing productId or expirationDate');
|
|
90
|
+
return renewalState;
|
|
91
|
+
}
|
|
88
92
|
await handleRenewal(
|
|
89
93
|
userId,
|
|
90
|
-
renewalResult.productId
|
|
91
|
-
renewalResult.newExpirationDate
|
|
94
|
+
renewalResult.productId,
|
|
95
|
+
renewalResult.newExpirationDate,
|
|
92
96
|
customerInfo,
|
|
93
97
|
config.onRenewalDetected
|
|
94
98
|
);
|
|
95
99
|
}
|
|
96
100
|
|
|
97
101
|
if (renewalResult.isPlanChange) {
|
|
102
|
+
if (!renewalResult.productId || !renewalResult.previousProductId) {
|
|
103
|
+
console.error('[CustomerInfoHandler] Invalid plan change state: missing productId(s)');
|
|
104
|
+
return renewalState;
|
|
105
|
+
}
|
|
98
106
|
await handlePlanChange(
|
|
99
107
|
userId,
|
|
100
|
-
renewalResult.productId
|
|
101
|
-
renewalResult.previousProductId
|
|
108
|
+
renewalResult.productId,
|
|
109
|
+
renewalResult.previousProductId,
|
|
102
110
|
renewalResult.isUpgrade,
|
|
103
111
|
customerInfo,
|
|
104
112
|
config.onPlanChanged
|
|
@@ -49,10 +49,12 @@ export async function syncPremiumStatus(
|
|
|
49
49
|
}
|
|
50
50
|
return { success: true };
|
|
51
51
|
} catch (error) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
if (__DEV__) {
|
|
53
|
+
console.error('[PremiumStatusSyncer] Premium status callback failed:', {
|
|
54
|
+
userId,
|
|
55
|
+
error: error instanceof Error ? error.message : String(error)
|
|
56
|
+
});
|
|
57
|
+
}
|
|
56
58
|
|
|
57
59
|
return {
|
|
58
60
|
success: false,
|
|
@@ -85,6 +87,8 @@ export async function notifyRestoreCompleted(
|
|
|
85
87
|
try {
|
|
86
88
|
await config.onRestoreCompleted({ userId, isPremium, customerInfo });
|
|
87
89
|
} catch (error) {
|
|
88
|
-
|
|
90
|
+
if (__DEV__) {
|
|
91
|
+
console.error('[PremiumStatusSyncer] Restore callback failed:', error instanceof Error ? error.message : String(error));
|
|
92
|
+
}
|
|
89
93
|
}
|
|
90
94
|
}
|
|
@@ -104,7 +104,8 @@ const ManagedSubscriptionFlowInner = React.memo<ManagedSubscriptionFlowProps>(({
|
|
|
104
104
|
|
|
105
105
|
const {
|
|
106
106
|
flowState,
|
|
107
|
-
setShowFeedback
|
|
107
|
+
setShowFeedback,
|
|
108
|
+
completeOnboarding
|
|
108
109
|
} = usePaywallOrchestrator({
|
|
109
110
|
navigation,
|
|
110
111
|
isNavReady,
|
|
@@ -165,7 +166,7 @@ const ManagedSubscriptionFlowInner = React.memo<ManagedSubscriptionFlowProps>(({
|
|
|
165
166
|
return (
|
|
166
167
|
<OnboardingScreen
|
|
167
168
|
slides={onboarding.slides}
|
|
168
|
-
onComplete={
|
|
169
|
+
onComplete={completeOnboarding}
|
|
169
170
|
showSkipButton={onboarding.showSkipButton ?? true}
|
|
170
171
|
showBackButton={onboarding.showBackButton ?? true}
|
|
171
172
|
showProgressBar={onboarding.showProgressBar ?? true}
|
|
@@ -105,6 +105,7 @@ export const useSubscriptionFlowStore = createStore<SubscriptionFlowState, Subsc
|
|
|
105
105
|
status: SubscriptionFlowStatus.INITIALIZING,
|
|
106
106
|
syncStatus: SyncStatus.IDLE,
|
|
107
107
|
syncError: null,
|
|
108
|
+
isInitialized: false, // Reset isInitialized to allow fresh initialization
|
|
108
109
|
isOnboardingComplete: false,
|
|
109
110
|
showPostOnboardingPaywall: false,
|
|
110
111
|
showFeedback: false,
|
|
@@ -114,22 +115,3 @@ export const useSubscriptionFlowStore = createStore<SubscriptionFlowState, Subsc
|
|
|
114
115
|
},
|
|
115
116
|
}),
|
|
116
117
|
});
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Hook for backward compatibility and easier consumption.
|
|
120
|
-
* Provides a unified object structure matching the previous implementation.
|
|
121
|
-
*/
|
|
122
|
-
export function useSubscriptionFlow(_userId?: string) {
|
|
123
|
-
const store = useSubscriptionFlowStore();
|
|
124
|
-
|
|
125
|
-
return {
|
|
126
|
-
state: store,
|
|
127
|
-
completeOnboarding: store.completeOnboarding,
|
|
128
|
-
closePostOnboardingPaywall: store.closePostOnboardingPaywall,
|
|
129
|
-
closeFeedback: store.closeFeedback,
|
|
130
|
-
setAuthModalOpen: store.setAuthModalOpen,
|
|
131
|
-
markPaywallShown: store.markPaywallShown,
|
|
132
|
-
setShowFeedback: store.setShowFeedback,
|
|
133
|
-
resetFlow: store.resetFlow,
|
|
134
|
-
};
|
|
135
|
-
}
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
// Domain Layer - Constants & Types
|
|
2
|
-
export
|
|
2
|
+
export {
|
|
3
|
+
USER_TIER,
|
|
4
|
+
SUBSCRIPTION_STATUS,
|
|
5
|
+
PERIOD_TYPE,
|
|
6
|
+
PACKAGE_TYPE,
|
|
7
|
+
PLATFORM,
|
|
8
|
+
PURCHASE_SOURCE,
|
|
9
|
+
PURCHASE_TYPE,
|
|
10
|
+
ANONYMOUS_CACHE_KEY,
|
|
11
|
+
} from "./domains/subscription/core/SubscriptionConstants";
|
|
12
|
+
export type {
|
|
13
|
+
UserTierType,
|
|
14
|
+
SubscriptionStatusType,
|
|
15
|
+
PeriodType,
|
|
16
|
+
PackageType,
|
|
17
|
+
Platform,
|
|
18
|
+
PurchaseSource,
|
|
19
|
+
PurchaseType,
|
|
20
|
+
} from "./domains/subscription/core/SubscriptionConstants";
|
|
3
21
|
export type { SubscriptionMetadata } from "./domains/subscription/core/types/SubscriptionMetadata";
|
|
4
22
|
export type { PremiumStatus } from "./domains/subscription/core/types/PremiumStatus";
|
|
5
23
|
export type { CreditInfo } from "./domains/subscription/core/types/CreditInfo";
|
|
@@ -45,20 +63,33 @@ export { useDeductCredit } from "./domains/credits/presentation/deduct-credit/us
|
|
|
45
63
|
export { useFeatureGate } from "./domains/subscription/presentation/useFeatureGate";
|
|
46
64
|
export { usePaywallVisibility, paywallControl } from "./domains/subscription/presentation/usePaywallVisibility";
|
|
47
65
|
export { usePremium } from "./domains/subscription/presentation/usePremium";
|
|
48
|
-
export {
|
|
66
|
+
export { useSubscriptionFlowStore } from "./domains/subscription/presentation/useSubscriptionFlow";
|
|
49
67
|
export type { SubscriptionFlowState, SubscriptionFlowActions, SubscriptionFlowStore } from "./domains/subscription/presentation/useSubscriptionFlow";
|
|
50
68
|
export { useSubscriptionStatus } from "./domains/subscription/presentation/useSubscriptionStatus";
|
|
51
|
-
export
|
|
52
|
-
export
|
|
53
|
-
export
|
|
69
|
+
export type { SubscriptionStatusResult } from "./domains/subscription/presentation/useSubscriptionStatus.types";
|
|
70
|
+
export { usePaywallFeedback } from "./presentation/hooks/feedback/usePaywallFeedback";
|
|
71
|
+
export {
|
|
72
|
+
usePaywallFeedbackSubmit,
|
|
73
|
+
useSettingsFeedbackSubmit,
|
|
74
|
+
} from "./presentation/hooks/feedback/useFeedbackSubmit";
|
|
75
|
+
export type {
|
|
76
|
+
UsePaywallFeedbackSubmitOptions,
|
|
77
|
+
SettingsFeedbackData,
|
|
78
|
+
UseSettingsFeedbackSubmitOptions,
|
|
79
|
+
} from "./presentation/hooks/feedback/useFeedbackSubmit";
|
|
54
80
|
|
|
55
81
|
// Presentation Layer - Components
|
|
56
|
-
export
|
|
82
|
+
export { PremiumDetailsCard } from "./domains/subscription/presentation/components/details/PremiumDetailsCard";
|
|
83
|
+
export type {
|
|
84
|
+
PremiumDetailsTranslations,
|
|
85
|
+
PremiumDetailsCardProps,
|
|
86
|
+
} from "./domains/subscription/presentation/components/details/PremiumDetailsCardTypes";
|
|
57
87
|
export { PremiumStatusBadge } from "./domains/subscription/presentation/components/details/PremiumStatusBadge";
|
|
58
88
|
export type { PremiumStatusBadgeProps } from "./domains/subscription/presentation/components/details/PremiumStatusBadge.types";
|
|
59
|
-
export
|
|
60
|
-
export
|
|
61
|
-
export
|
|
89
|
+
export { SubscriptionSection } from "./domains/subscription/presentation/components/sections/SubscriptionSection";
|
|
90
|
+
export type { SubscriptionSectionProps } from "./domains/subscription/presentation/components/sections/SubscriptionSection.types";
|
|
91
|
+
export { PaywallFeedbackScreen } from "./domains/subscription/presentation/components/feedback/PaywallFeedbackScreen";
|
|
92
|
+
export type { PaywallFeedbackScreenProps } from "./domains/subscription/presentation/components/feedback/PaywallFeedbackScreen.types";
|
|
62
93
|
export type {
|
|
63
94
|
SubscriptionDetailConfig,
|
|
64
95
|
SubscriptionDetailTranslations,
|
|
@@ -79,11 +110,47 @@ export type {
|
|
|
79
110
|
} from "./domains/credits/core/Credits";
|
|
80
111
|
|
|
81
112
|
// Utils
|
|
82
|
-
export
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
export
|
|
113
|
+
export {
|
|
114
|
+
getCreditAllocation,
|
|
115
|
+
createCreditAmountsFromPackages,
|
|
116
|
+
} from "./utils/creditMapper";
|
|
117
|
+
export {
|
|
118
|
+
isCreditPackage,
|
|
119
|
+
detectPackageType,
|
|
120
|
+
} from "./utils/packageTypeDetector";
|
|
121
|
+
export type { SubscriptionPackageType } from "./utils/packageTypeDetector";
|
|
122
|
+
export {
|
|
123
|
+
formatPrice,
|
|
124
|
+
getBillingPeriodSuffix,
|
|
125
|
+
formatPriceWithPeriod,
|
|
126
|
+
} from "./utils/priceUtils";
|
|
127
|
+
export type { UserTierInfo, PremiumStatusFetcher } from "./utils/types";
|
|
128
|
+
export type { DateLike } from "./utils/dateUtils.core";
|
|
129
|
+
export {
|
|
130
|
+
isNow,
|
|
131
|
+
isPast,
|
|
132
|
+
isFuture,
|
|
133
|
+
isValidDate,
|
|
134
|
+
toSafeDate,
|
|
135
|
+
formatISO,
|
|
136
|
+
now,
|
|
137
|
+
} from "./utils/dateUtils.core";
|
|
138
|
+
export {
|
|
139
|
+
daysBetween,
|
|
140
|
+
daysUntil,
|
|
141
|
+
isSameDay,
|
|
142
|
+
isToday,
|
|
143
|
+
} from "./utils/dateUtils.compare";
|
|
144
|
+
export {
|
|
145
|
+
formatRelative,
|
|
146
|
+
formatShort,
|
|
147
|
+
formatLong,
|
|
148
|
+
} from "./utils/dateUtils.format";
|
|
149
|
+
export {
|
|
150
|
+
addDays,
|
|
151
|
+
addMonths,
|
|
152
|
+
addYears,
|
|
153
|
+
} from "./utils/dateUtils.math";
|
|
87
154
|
export { getAppVersion, validatePlatform } from "./utils/appUtils";
|
|
88
155
|
export { toDate, toISOString, toTimestamp, getCurrentISOString } from "./shared/utils/dateConverter";
|
|
89
156
|
|