@umituz/react-native-subscription 2.27.112 → 2.27.114
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/CreditsInitializer.ts +28 -125
- package/src/domains/credits/application/credit-strategies/{CreditAllocationContext.ts → CreditAllocationOrchestrator.ts} +4 -9
- package/src/domains/credits/application/creditDocumentHelpers.ts +58 -0
- package/src/domains/credits/application/creditOperationUtils.ts +154 -0
- package/src/domains/credits/core/CreditsMapper.ts +8 -13
- package/src/domains/credits/infrastructure/{CreditsRepositoryProvider.ts → CreditsRepositoryManager.ts} +2 -2
- package/src/domains/credits/presentation/useCredits.ts +2 -3
- package/src/domains/credits/presentation/useDeductCredit.ts +4 -4
- package/src/domains/paywall/components/PaywallContainer.types.ts +1 -1
- package/src/domains/paywall/components/PaywallModal.tsx +28 -52
- package/src/domains/paywall/hooks/usePaywallActions.ts +77 -33
- package/src/domains/subscription/application/SubscriptionInitializer.ts +1 -1
- package/src/domains/subscription/application/SubscriptionSyncService.ts +17 -21
- package/src/domains/subscription/core/RevenueCatError.ts +40 -31
- package/src/domains/subscription/infrastructure/handlers/PackageHandler.ts +0 -1
- package/src/domains/subscription/infrastructure/hooks/useRevenueCatTrialEligibility.ts +19 -85
- package/src/domains/subscription/infrastructure/managers/SubscriptionManager.ts +33 -75
- package/src/domains/subscription/infrastructure/managers/subscriptionManagerUtils.ts +57 -0
- package/src/domains/subscription/infrastructure/services/CustomerInfoListenerManager.ts +3 -12
- package/src/domains/subscription/infrastructure/services/PurchaseHandler.ts +0 -2
- package/src/domains/subscription/infrastructure/services/RevenueCatInitializer.ts +2 -4
- package/src/domains/subscription/infrastructure/services/RevenueCatService.ts +1 -5
- package/src/domains/subscription/infrastructure/utils/PremiumStatusSyncer.ts +3 -12
- package/src/domains/subscription/infrastructure/utils/authPurchaseState.ts +69 -0
- package/src/domains/subscription/infrastructure/utils/trialEligibilityUtils.ts +77 -0
- package/src/domains/subscription/presentation/components/feedback/FeedbackOption.tsx +139 -0
- package/src/domains/subscription/presentation/components/feedback/PaywallFeedbackModal.tsx +15 -70
- package/src/domains/subscription/presentation/components/feedback/paywallFeedbackStyles.ts +0 -92
- package/src/domains/subscription/presentation/screens/SubscriptionDetailScreen.tsx +1 -1
- package/src/domains/subscription/presentation/stores/purchaseLoadingStore.ts +1 -18
- package/src/domains/subscription/presentation/useAuthAwarePurchase.ts +19 -69
- package/src/domains/subscription/presentation/usePaywallVisibility.ts +1 -1
- package/src/domains/subscription/presentation/usePremium.ts +2 -11
- package/src/domains/subscription/presentation/useSubscriptionStatus.ts +1 -6
- package/src/domains/trial/application/TrialService.ts +4 -8
- package/src/domains/wallet/index.ts +0 -6
- package/src/domains/wallet/infrastructure/repositories/TransactionRepository.ts +1 -1
- package/src/domains/wallet/infrastructure/services/ProductMetadataService.ts +0 -13
- package/src/domains/wallet/presentation/hooks/useProductMetadata.ts +0 -10
- package/src/domains/wallet/presentation/hooks/useTransactionHistory.ts +0 -8
- package/src/domains/wallet/presentation/screens/WalletScreen.tsx +57 -43
- package/src/index.ts +1 -1
- package/src/init/createSubscriptionInitModule.ts +1 -4
- package/src/presentation/hooks/feedback/useFeedbackSubmit.ts +0 -14
- package/src/shared/application/ActivationHandler.ts +6 -6
- package/src/shared/application/FeedbackService.ts +0 -21
- package/src/shared/infrastructure/SubscriptionEventBus.ts +1 -2
- package/src/shared/presentation/index.ts +1 -0
- package/src/shared/presentation/layouts/ScreenLayout.tsx +79 -0
- package/src/shared/types/CommonTypes.ts +65 -0
- package/src/shared/utils/BaseError.ts +26 -0
- package/src/shared/utils/Logger.ts +15 -46
- package/src/shared/utils/Result.ts +16 -0
- package/src/shared/utils/SubscriptionConfig.ts +1 -1
- package/src/shared/utils/SubscriptionError.ts +20 -30
- package/src/utils/appUtils.ts +34 -0
- package/src/utils/dateUtils.ts +32 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/packageTypeDetector.ts +0 -4
- package/src/domains/wallet/presentation/screens/WalletScreenContainer.tsx +0 -88
|
@@ -9,13 +9,15 @@
|
|
|
9
9
|
import React from "react";
|
|
10
10
|
import { View, StyleSheet, TouchableOpacity } from "react-native";
|
|
11
11
|
import {
|
|
12
|
-
useSafeAreaInsets,
|
|
13
12
|
useAppDesignTokens,
|
|
14
13
|
AtomicText,
|
|
15
14
|
AtomicIcon,
|
|
16
15
|
AtomicSpinner,
|
|
17
|
-
ScreenLayout,
|
|
18
16
|
} from "@umituz/react-native-design-system";
|
|
17
|
+
import { ScreenLayout } from "../../../../shared/presentation";
|
|
18
|
+
import { useNavigation } from "@react-navigation/native";
|
|
19
|
+
import { useWallet } from "../hooks/useWallet";
|
|
20
|
+
import { getWalletConfig } from "../../infrastructure/config/walletConfig";
|
|
19
21
|
import {
|
|
20
22
|
BalanceCard,
|
|
21
23
|
type BalanceCardTranslations,
|
|
@@ -24,7 +26,6 @@ import {
|
|
|
24
26
|
TransactionList,
|
|
25
27
|
type TransactionListTranslations,
|
|
26
28
|
} from "../components/TransactionList";
|
|
27
|
-
import type { CreditLog } from "../../domain/types/transaction.types";
|
|
28
29
|
|
|
29
30
|
export interface WalletScreenTranslations
|
|
30
31
|
extends BalanceCardTranslations,
|
|
@@ -32,58 +33,72 @@ export interface WalletScreenTranslations
|
|
|
32
33
|
screenTitle: string;
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
export interface
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
transactionsLoading: boolean;
|
|
40
|
-
translations: WalletScreenTranslations;
|
|
36
|
+
export interface WalletScreenProps {
|
|
37
|
+
/** Translations (overrides global config) */
|
|
38
|
+
translations?: WalletScreenTranslations;
|
|
39
|
+
/** Override onBack handler (default: navigation.goBack) */
|
|
41
40
|
onBack?: () => void;
|
|
41
|
+
/** Custom date formatter */
|
|
42
42
|
dateFormatter?: (timestamp: number) => string;
|
|
43
|
-
|
|
44
|
-
balanceIconName?: string;
|
|
43
|
+
/** Footer component */
|
|
45
44
|
footer?: React.ReactNode;
|
|
46
45
|
}
|
|
47
46
|
|
|
48
|
-
export
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
export const WalletScreen: React.FC<WalletScreenProps> = ({
|
|
48
|
+
translations,
|
|
49
|
+
onBack,
|
|
50
|
+
dateFormatter,
|
|
51
|
+
footer,
|
|
52
|
+
}) => {
|
|
53
53
|
const tokens = useAppDesignTokens();
|
|
54
|
-
const
|
|
54
|
+
const navigation = useNavigation();
|
|
55
|
+
const config = getWalletConfig();
|
|
56
|
+
|
|
57
|
+
const {
|
|
58
|
+
balance,
|
|
59
|
+
balanceLoading,
|
|
60
|
+
transactions,
|
|
61
|
+
transactionsLoading,
|
|
62
|
+
} = useWallet({
|
|
63
|
+
transactionConfig: {
|
|
64
|
+
collectionName: config.transactionCollection,
|
|
65
|
+
useUserSubcollection: config.useUserSubcollection,
|
|
66
|
+
},
|
|
67
|
+
transactionLimit: config.transactionLimit,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const activeTranslations = translations ?? config.translations;
|
|
71
|
+
const handleBack = onBack ?? (() => navigation.goBack());
|
|
55
72
|
|
|
56
73
|
const renderHeader = () => (
|
|
57
|
-
<View style={[styles.header, { paddingTop:
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
</TouchableOpacity>
|
|
70
|
-
)}
|
|
74
|
+
<View style={[styles.header, { paddingTop: 12 }]}>
|
|
75
|
+
<TouchableOpacity
|
|
76
|
+
onPress={handleBack}
|
|
77
|
+
style={styles.backButton}
|
|
78
|
+
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
|
|
79
|
+
>
|
|
80
|
+
<AtomicIcon
|
|
81
|
+
name="arrow-left"
|
|
82
|
+
size="lg"
|
|
83
|
+
customColor={tokens.colors.textPrimary}
|
|
84
|
+
/>
|
|
85
|
+
</TouchableOpacity>
|
|
71
86
|
<AtomicText
|
|
72
87
|
type="titleLarge"
|
|
73
88
|
style={{ color: tokens.colors.textPrimary, fontWeight: "700" }}
|
|
74
89
|
>
|
|
75
|
-
{
|
|
90
|
+
{activeTranslations.screenTitle}
|
|
76
91
|
</AtomicText>
|
|
77
92
|
</View>
|
|
78
93
|
);
|
|
79
94
|
|
|
80
95
|
const renderBalance = () => {
|
|
81
|
-
if (
|
|
96
|
+
if (balanceLoading) {
|
|
82
97
|
return (
|
|
83
98
|
<AtomicSpinner
|
|
84
99
|
size="xl"
|
|
85
100
|
color="primary"
|
|
86
|
-
text={
|
|
101
|
+
text={activeTranslations.loading}
|
|
87
102
|
fullContainer
|
|
88
103
|
style={styles.loadingContainer}
|
|
89
104
|
/>
|
|
@@ -92,8 +107,8 @@ export const WalletScreen: React.FC<WalletScreenProps> = ({ config }) => {
|
|
|
92
107
|
|
|
93
108
|
return (
|
|
94
109
|
<BalanceCard
|
|
95
|
-
balance={
|
|
96
|
-
translations={
|
|
110
|
+
balance={balance}
|
|
111
|
+
translations={activeTranslations}
|
|
97
112
|
iconName={config.balanceIconName}
|
|
98
113
|
/>
|
|
99
114
|
);
|
|
@@ -102,19 +117,18 @@ export const WalletScreen: React.FC<WalletScreenProps> = ({ config }) => {
|
|
|
102
117
|
return (
|
|
103
118
|
<ScreenLayout
|
|
104
119
|
scrollable={true}
|
|
105
|
-
edges={["bottom"]}
|
|
120
|
+
edges={["top", "bottom"]}
|
|
106
121
|
backgroundColor={tokens.colors.backgroundPrimary}
|
|
107
122
|
contentContainerStyle={styles.content}
|
|
108
|
-
footer={
|
|
123
|
+
footer={footer}
|
|
109
124
|
>
|
|
110
125
|
{renderHeader()}
|
|
111
126
|
{renderBalance()}
|
|
112
127
|
<TransactionList
|
|
113
|
-
transactions={
|
|
114
|
-
loading={
|
|
115
|
-
translations={
|
|
116
|
-
|
|
117
|
-
dateFormatter={config.dateFormatter}
|
|
128
|
+
transactions={transactions}
|
|
129
|
+
loading={transactionsLoading}
|
|
130
|
+
translations={activeTranslations}
|
|
131
|
+
dateFormatter={dateFormatter}
|
|
118
132
|
/>
|
|
119
133
|
</ScreenLayout>
|
|
120
134
|
);
|
package/src/index.ts
CHANGED
|
@@ -48,7 +48,7 @@ export {
|
|
|
48
48
|
getCreditsRepository,
|
|
49
49
|
getCreditsConfig,
|
|
50
50
|
isCreditsRepositoryConfigured
|
|
51
|
-
} from "./domains/credits/infrastructure/
|
|
51
|
+
} from "./domains/credits/infrastructure/CreditsRepositoryManager";
|
|
52
52
|
|
|
53
53
|
// Presentation Layer - Hooks (Point to the bridge)
|
|
54
54
|
export * from "./presentation/hooks";
|
|
@@ -18,15 +18,12 @@ export function createSubscriptionInitModule(config: SubscriptionInitModuleConfi
|
|
|
18
18
|
try {
|
|
19
19
|
const apiKey = getApiKey();
|
|
20
20
|
if (!apiKey) {
|
|
21
|
-
if (__DEV__) console.log('[SubscriptionInit] No API key - skipping');
|
|
22
21
|
return true;
|
|
23
22
|
}
|
|
24
23
|
|
|
25
24
|
await initializeSubscription({ apiKey, ...subscriptionConfig });
|
|
26
|
-
if (__DEV__) console.log('[SubscriptionInit] Initialized');
|
|
27
25
|
return true;
|
|
28
|
-
} catch
|
|
29
|
-
if (__DEV__) console.error('[SubscriptionInit] Error:', error);
|
|
26
|
+
} catch {
|
|
30
27
|
return false;
|
|
31
28
|
}
|
|
32
29
|
},
|
|
@@ -27,13 +27,6 @@ export function usePaywallFeedbackSubmit(
|
|
|
27
27
|
|
|
28
28
|
const submit = useCallback(
|
|
29
29
|
async (reason: string) => {
|
|
30
|
-
if (__DEV__) {
|
|
31
|
-
console.log("[usePaywallFeedbackSubmit] Submitting:", {
|
|
32
|
-
userId: user?.uid?.slice(0, 8),
|
|
33
|
-
reason: reason.slice(0, 20),
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
|
|
37
30
|
try {
|
|
38
31
|
const result = await submitPaywallFeedback(
|
|
39
32
|
user?.uid ?? null,
|
|
@@ -81,13 +74,6 @@ export function useSettingsFeedbackSubmit(
|
|
|
81
74
|
|
|
82
75
|
const submit = useCallback(
|
|
83
76
|
async (data: SettingsFeedbackData) => {
|
|
84
|
-
if (__DEV__) {
|
|
85
|
-
console.log("[useSettingsFeedbackSubmit] Submitting:", {
|
|
86
|
-
userId: user?.uid?.slice(0, 8),
|
|
87
|
-
type: data.type,
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
|
|
91
77
|
try {
|
|
92
78
|
const result = await submitSettingsFeedback(
|
|
93
79
|
user?.uid ?? null,
|
|
@@ -9,7 +9,7 @@ export interface ActivationHandlerConfig {
|
|
|
9
9
|
userId: string,
|
|
10
10
|
status: SubscriptionStatus
|
|
11
11
|
) => Promise<void> | void;
|
|
12
|
-
onError?: (error: Error,
|
|
12
|
+
onError?: (error: Error, operation: string) => Promise<void> | void;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
/**
|
|
@@ -85,15 +85,15 @@ async function notifyStatusChange(
|
|
|
85
85
|
* Safe error handler - wraps error callbacks to prevent secondary failures
|
|
86
86
|
*/
|
|
87
87
|
export async function safeHandleError(
|
|
88
|
-
onError: ((error: Error,
|
|
88
|
+
onError: ((error: Error, operation: string) => Promise<void> | void) | undefined,
|
|
89
89
|
error: unknown,
|
|
90
|
-
|
|
90
|
+
operation: string
|
|
91
91
|
): Promise<void> {
|
|
92
92
|
if (!onError) return;
|
|
93
93
|
|
|
94
94
|
try {
|
|
95
95
|
const err = error instanceof Error ? error : new Error("Unknown error");
|
|
96
|
-
await onError(err,
|
|
96
|
+
await onError(err, operation);
|
|
97
97
|
} catch {
|
|
98
98
|
// Ignore callback errors
|
|
99
99
|
}
|
|
@@ -102,7 +102,7 @@ export async function safeHandleError(
|
|
|
102
102
|
async function handleError(
|
|
103
103
|
config: ActivationHandlerConfig,
|
|
104
104
|
error: unknown,
|
|
105
|
-
|
|
105
|
+
operation: string
|
|
106
106
|
): Promise<void> {
|
|
107
|
-
await safeHandleError(config.onError, error, `ActivationHandler.${
|
|
107
|
+
await safeHandleError(config.onError, error, `ActivationHandler.${operation}`);
|
|
108
108
|
}
|
|
@@ -32,30 +32,16 @@ export async function submitFeedback(
|
|
|
32
32
|
const db = getFirestore();
|
|
33
33
|
|
|
34
34
|
if (!db) {
|
|
35
|
-
if (__DEV__) {
|
|
36
|
-
console.warn("[FeedbackService] Firestore not available");
|
|
37
|
-
}
|
|
38
35
|
return { success: false, error: new Error("Firestore not available") };
|
|
39
36
|
}
|
|
40
37
|
|
|
41
38
|
if (!data.userId) {
|
|
42
|
-
if (__DEV__) {
|
|
43
|
-
console.warn("[FeedbackService] User ID is required for feedback");
|
|
44
|
-
}
|
|
45
39
|
return { success: false, error: new Error("User ID is required") };
|
|
46
40
|
}
|
|
47
41
|
|
|
48
42
|
try {
|
|
49
|
-
if (__DEV__) {
|
|
50
|
-
console.log("[FeedbackService] Submitting feedback:", {
|
|
51
|
-
type: data.type,
|
|
52
|
-
userId: data.userId?.slice(0, 8),
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
|
|
56
43
|
const now = new Date().toISOString();
|
|
57
44
|
|
|
58
|
-
// Store under users/{userId}/feedback
|
|
59
45
|
const userDocRef = doc(db, "users", data.userId);
|
|
60
46
|
const feedbackCollectionRef = collection(userDocRef, "feedback");
|
|
61
47
|
|
|
@@ -70,15 +56,8 @@ export async function submitFeedback(
|
|
|
70
56
|
updatedAt: now,
|
|
71
57
|
});
|
|
72
58
|
|
|
73
|
-
if (__DEV__) {
|
|
74
|
-
console.log("[FeedbackService] Feedback submitted successfully");
|
|
75
|
-
}
|
|
76
|
-
|
|
77
59
|
return { success: true };
|
|
78
60
|
} catch (error) {
|
|
79
|
-
if (__DEV__) {
|
|
80
|
-
console.error("[FeedbackService] Submit error:", error);
|
|
81
|
-
}
|
|
82
61
|
return {
|
|
83
62
|
success: false,
|
|
84
63
|
error: error instanceof Error ? error : new Error("Unknown error"),
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./layouts/ScreenLayout";
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Screen Layout Component
|
|
3
|
+
* Centralized layout with safe area handling and consistent styling.
|
|
4
|
+
*/
|
|
5
|
+
import React from "react";
|
|
6
|
+
import { StyleSheet, View, ScrollView, type ViewStyle, type ColorValue } from "react-native";
|
|
7
|
+
import { useSafeAreaInsets, type Edge } from "react-native-safe-area-context";
|
|
8
|
+
import { useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
9
|
+
|
|
10
|
+
export interface ScreenLayoutProps {
|
|
11
|
+
children: React.ReactNode;
|
|
12
|
+
scrollable?: boolean;
|
|
13
|
+
edges?: Edge[];
|
|
14
|
+
backgroundColor?: ColorValue;
|
|
15
|
+
contentContainerStyle?: ViewStyle;
|
|
16
|
+
footer?: React.ReactNode;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const ScreenLayout: React.FC<ScreenLayoutProps> = ({
|
|
20
|
+
children,
|
|
21
|
+
scrollable = false,
|
|
22
|
+
edges = ["top", "bottom", "left", "right"],
|
|
23
|
+
backgroundColor,
|
|
24
|
+
contentContainerStyle,
|
|
25
|
+
footer,
|
|
26
|
+
}) => {
|
|
27
|
+
const tokens = useAppDesignTokens();
|
|
28
|
+
const insets = useSafeAreaInsets();
|
|
29
|
+
|
|
30
|
+
const containerStyle = [
|
|
31
|
+
styles.container,
|
|
32
|
+
{
|
|
33
|
+
backgroundColor: backgroundColor ?? tokens.colors.backgroundPrimary,
|
|
34
|
+
paddingTop: edges.includes("top") ? insets.top : 0,
|
|
35
|
+
paddingBottom: edges.includes("bottom") ? insets.bottom : 0,
|
|
36
|
+
paddingLeft: edges.includes("left") ? insets.left : 0,
|
|
37
|
+
paddingRight: edges.includes("right") ? insets.right : 0,
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const content = (
|
|
42
|
+
<>
|
|
43
|
+
<View style={[styles.flex, contentContainerStyle]}>
|
|
44
|
+
{children}
|
|
45
|
+
</View>
|
|
46
|
+
{footer}
|
|
47
|
+
</>
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
if (scrollable) {
|
|
51
|
+
return (
|
|
52
|
+
<ScrollView
|
|
53
|
+
style={containerStyle}
|
|
54
|
+
contentContainerStyle={[styles.scrollContent]}
|
|
55
|
+
showsVerticalScrollIndicator={false}
|
|
56
|
+
>
|
|
57
|
+
{content}
|
|
58
|
+
</ScrollView>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<View style={containerStyle}>
|
|
64
|
+
{content}
|
|
65
|
+
</View>
|
|
66
|
+
);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const styles = StyleSheet.create({
|
|
70
|
+
container: {
|
|
71
|
+
flex: 1,
|
|
72
|
+
},
|
|
73
|
+
scrollContent: {
|
|
74
|
+
flexGrow: 1,
|
|
75
|
+
},
|
|
76
|
+
flex: {
|
|
77
|
+
flex: 1,
|
|
78
|
+
},
|
|
79
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Common Type Definitions
|
|
3
|
+
* Shared types used across multiple domains
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Purchase result from any purchase operation
|
|
8
|
+
*/
|
|
9
|
+
export interface PurchaseResult {
|
|
10
|
+
success: boolean;
|
|
11
|
+
productId?: string;
|
|
12
|
+
error?: Error;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Transaction result for repository operations
|
|
17
|
+
*/
|
|
18
|
+
export interface TransactionResult<T = unknown> {
|
|
19
|
+
success: boolean;
|
|
20
|
+
data?: T;
|
|
21
|
+
error?: Error;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Subscription status information
|
|
26
|
+
*/
|
|
27
|
+
export interface SubscriptionStatusInfo {
|
|
28
|
+
isActive: boolean;
|
|
29
|
+
isExpired: boolean;
|
|
30
|
+
isTrial: boolean;
|
|
31
|
+
willRenew: boolean;
|
|
32
|
+
expirationDate?: Date;
|
|
33
|
+
productId?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Credits information
|
|
38
|
+
*/
|
|
39
|
+
export interface CreditsInfo {
|
|
40
|
+
credits: number;
|
|
41
|
+
creditLimit: number;
|
|
42
|
+
isPremium: boolean;
|
|
43
|
+
status: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Transaction metadata for any transaction type
|
|
48
|
+
*/
|
|
49
|
+
export interface TransactionMetadata {
|
|
50
|
+
productId: string;
|
|
51
|
+
amount: number;
|
|
52
|
+
currency?: string;
|
|
53
|
+
timestamp: Date;
|
|
54
|
+
type: 'purchase' | 'renewal' | 'restore' | 'credit_purchase';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Platform information
|
|
59
|
+
*/
|
|
60
|
+
export type Platform = 'ios' | 'android';
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Purchase source tracking
|
|
64
|
+
*/
|
|
65
|
+
export type PurchaseSource = 'settings' | 'paywall' | 'upgrade_prompt' | 'auto-execution' | 'manual';
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base Error Class
|
|
3
|
+
* Common base error for all domain errors
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export abstract class BaseError extends Error {
|
|
7
|
+
public readonly code: string;
|
|
8
|
+
public readonly cause?: Error;
|
|
9
|
+
|
|
10
|
+
constructor(message: string, code: string, cause?: Error) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = this.constructor.name;
|
|
13
|
+
this.code = code;
|
|
14
|
+
this.cause = cause;
|
|
15
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
toJSON() {
|
|
19
|
+
return {
|
|
20
|
+
name: this.name,
|
|
21
|
+
code: this.code,
|
|
22
|
+
message: this.message,
|
|
23
|
+
cause: this.cause?.message,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Subscription Logger
|
|
3
3
|
* Centralized logging utility for the subscription package.
|
|
4
|
-
* All logs are
|
|
4
|
+
* All logs are disabled - no logging.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
export type LogLevel = "debug" | "info" | "warn" | "error";
|
|
8
8
|
|
|
9
|
-
export interface
|
|
9
|
+
export interface LogMetadata {
|
|
10
10
|
[key: string]: unknown;
|
|
11
11
|
}
|
|
12
12
|
|
|
@@ -25,86 +25,55 @@ export type LogCategory = (typeof LOG_CATEGORY)[keyof typeof LOG_CATEGORY];
|
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* Development-only logger that automatically respects __DEV__ flag.
|
|
28
|
-
* All methods are no-ops
|
|
28
|
+
* All methods are no-ops - logging is disabled.
|
|
29
29
|
*/
|
|
30
30
|
class SubscriptionLogger {
|
|
31
|
-
private enabled: boolean = true;
|
|
32
|
-
private categories: Set<LogCategory> = new Set();
|
|
33
|
-
|
|
34
31
|
/** Enable or disable all logging */
|
|
35
|
-
setEnabled(
|
|
36
|
-
this.enabled = enabled;
|
|
32
|
+
setEnabled(_enabled: boolean): void {
|
|
37
33
|
}
|
|
38
34
|
|
|
39
35
|
/** Enable logging for specific categories only */
|
|
40
|
-
setCategories(
|
|
41
|
-
this.categories = new Set(categories);
|
|
36
|
+
setCategories(_categories: LogCategory[]): void {
|
|
42
37
|
}
|
|
43
38
|
|
|
44
39
|
/** Clear category filter (log all categories) */
|
|
45
40
|
clearCategories(): void {
|
|
46
|
-
this.categories.clear();
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
private shouldLog(category?: LogCategory): boolean {
|
|
50
|
-
if (typeof __DEV__ === "undefined" || !__DEV__) return false;
|
|
51
|
-
if (!this.enabled) return false;
|
|
52
|
-
if (this.categories.size > 0 && category && !this.categories.has(category)) return false;
|
|
53
|
-
return true;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
private formatMessage(category: LogCategory | string, message: string): string {
|
|
57
|
-
return `[${category}] ${message}`;
|
|
58
41
|
}
|
|
59
42
|
|
|
60
|
-
debug(
|
|
61
|
-
if (!this.shouldLog(category)) return;
|
|
62
|
-
console.log(this.formatMessage(category, message), context ?? "");
|
|
43
|
+
debug(_category: LogCategory, _message: string, _metadata?: LogMetadata): void {
|
|
63
44
|
}
|
|
64
45
|
|
|
65
|
-
info(
|
|
66
|
-
if (!this.shouldLog(category)) return;
|
|
67
|
-
console.log(this.formatMessage(category, message), context ?? "");
|
|
46
|
+
info(_category: LogCategory, _message: string, _metadata?: LogMetadata): void {
|
|
68
47
|
}
|
|
69
48
|
|
|
70
|
-
warn(
|
|
71
|
-
if (!this.shouldLog(category)) return;
|
|
72
|
-
console.warn(this.formatMessage(category, message), context ?? "");
|
|
49
|
+
warn(_category: LogCategory, _message: string, _metadata?: LogMetadata): void {
|
|
73
50
|
}
|
|
74
51
|
|
|
75
|
-
error(
|
|
76
|
-
if (!this.shouldLog(category)) return;
|
|
77
|
-
console.error(this.formatMessage(category, message), { error, ...context });
|
|
52
|
+
error(_category: LogCategory, _message: string, _error?: unknown, _metadata?: LogMetadata): void {
|
|
78
53
|
}
|
|
79
54
|
|
|
80
55
|
/** Log purchase flow events */
|
|
81
|
-
purchase(
|
|
82
|
-
this.debug(LOG_CATEGORY.PURCHASE, message, context);
|
|
56
|
+
purchase(_message: string, _metadata?: LogMetadata): void {
|
|
83
57
|
}
|
|
84
58
|
|
|
85
59
|
/** Log credits-related events */
|
|
86
|
-
credits(
|
|
87
|
-
this.debug(LOG_CATEGORY.CREDITS, message, context);
|
|
60
|
+
credits(_message: string, _metadata?: LogMetadata): void {
|
|
88
61
|
}
|
|
89
62
|
|
|
90
63
|
/** Log trial-related events */
|
|
91
|
-
trial(
|
|
92
|
-
this.debug(LOG_CATEGORY.TRIAL, message, context);
|
|
64
|
+
trial(_message: string, _metadata?: LogMetadata): void {
|
|
93
65
|
}
|
|
94
66
|
|
|
95
67
|
/** Log RevenueCat SDK events */
|
|
96
|
-
revenueCat(
|
|
97
|
-
this.debug(LOG_CATEGORY.REVENUECAT, message, context);
|
|
68
|
+
revenueCat(_message: string, _metadata?: LogMetadata): void {
|
|
98
69
|
}
|
|
99
70
|
|
|
100
71
|
/** Log feature gate events */
|
|
101
|
-
featureGate(
|
|
102
|
-
this.debug(LOG_CATEGORY.FEATURE_GATE, message, context);
|
|
72
|
+
featureGate(_message: string, _metadata?: LogMetadata): void {
|
|
103
73
|
}
|
|
104
74
|
|
|
105
75
|
/** Log sync operations */
|
|
106
|
-
sync(
|
|
107
|
-
this.debug(LOG_CATEGORY.SYNC, message, context);
|
|
76
|
+
sync(_message: string, _metadata?: LogMetadata): void {
|
|
108
77
|
}
|
|
109
78
|
}
|
|
110
79
|
|
|
@@ -114,6 +114,22 @@ export function tryCatchSync<T>(
|
|
|
114
114
|
return failure(mappedError);
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
|
+
export function tryCatchSync<T>(
|
|
118
|
+
fn: () => T,
|
|
119
|
+
errorMapper?: (error: unknown) => Error
|
|
120
|
+
): Result<T, Error> {
|
|
121
|
+
try {
|
|
122
|
+
const data = fn();
|
|
123
|
+
return success(data);
|
|
124
|
+
} catch {
|
|
125
|
+
const mappedError = errorMapper
|
|
126
|
+
? errorMapper(error)
|
|
127
|
+
: error instanceof Error
|
|
128
|
+
? error
|
|
129
|
+
: new Error(String(error));
|
|
130
|
+
return failure(mappedError);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
117
133
|
|
|
118
134
|
/** Combine multiple results into one */
|
|
119
135
|
export function combine<T, E>(results: Result<T, E>[]): Result<T[], E> {
|
|
@@ -11,5 +11,5 @@ export interface SubscriptionConfig {
|
|
|
11
11
|
entitlements?: string[];
|
|
12
12
|
debugMode?: boolean;
|
|
13
13
|
onStatusChanged?: (userId: string, status: SubscriptionStatus) => Promise<void> | void;
|
|
14
|
-
onError?: (error: Error,
|
|
14
|
+
onError?: (error: Error, operation: string) => Promise<void> | void;
|
|
15
15
|
}
|