@umituz/react-native-subscription 2.14.97 → 2.14.99
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/LICENSE +21 -0
- package/README.md +461 -0
- package/package.json +1 -3
- package/src/application/README.md +229 -0
- package/src/application/ports/README.md +103 -0
- package/src/domain/README.md +402 -0
- package/src/domain/constants/README.md +80 -0
- package/src/domain/entities/README.md +176 -0
- package/src/domain/errors/README.md +307 -0
- package/src/domain/value-objects/README.md +186 -0
- package/src/domains/README.md +240 -0
- package/src/domains/config/README.md +390 -0
- package/src/domains/config/domain/README.md +390 -0
- package/src/domains/config/domain/entities/README.md +350 -0
- package/src/domains/paywall/README.md +371 -0
- package/src/domains/paywall/components/PaywallHeader.tsx +8 -11
- package/src/domains/paywall/components/README.md +185 -0
- package/src/domains/paywall/entities/README.md +199 -0
- package/src/domains/paywall/hooks/README.md +129 -0
- package/src/domains/wallet/README.md +292 -0
- package/src/domains/wallet/domain/README.md +108 -0
- package/src/domains/wallet/domain/entities/README.md +122 -0
- package/src/domains/wallet/domain/errors/README.md +157 -0
- package/src/domains/wallet/infrastructure/README.md +96 -0
- package/src/domains/wallet/presentation/components/BalanceCard.tsx +6 -12
- package/src/domains/wallet/presentation/components/README.md +231 -0
- package/src/domains/wallet/presentation/hooks/README.md +255 -0
- package/src/infrastructure/README.md +514 -0
- package/src/infrastructure/mappers/README.md +34 -0
- package/src/infrastructure/models/README.md +26 -0
- package/src/infrastructure/repositories/README.md +385 -0
- package/src/infrastructure/services/README.md +374 -0
- package/src/presentation/README.md +410 -0
- package/src/presentation/components/README.md +183 -0
- package/src/presentation/components/details/CreditRow.md +337 -0
- package/src/presentation/components/details/DetailRow.md +283 -0
- package/src/presentation/components/details/PremiumDetailsCard.md +266 -0
- package/src/presentation/components/details/PremiumStatusBadge.md +266 -0
- package/src/presentation/components/details/README.md +449 -0
- package/src/presentation/components/feedback/PaywallFeedbackModal.md +314 -0
- package/src/presentation/components/feedback/README.md +447 -0
- package/src/presentation/components/paywall/PaywallModal.md +444 -0
- package/src/presentation/components/paywall/README.md +190 -0
- package/src/presentation/components/sections/README.md +468 -0
- package/src/presentation/components/sections/SubscriptionSection.md +246 -0
- package/src/presentation/hooks/README.md +743 -0
- package/src/presentation/hooks/useAuthAwarePurchase.md +359 -0
- package/src/presentation/hooks/useAuthGate.md +403 -0
- package/src/presentation/hooks/useAuthSubscriptionSync.md +398 -0
- package/src/presentation/hooks/useCreditChecker.md +407 -0
- package/src/presentation/hooks/useCredits.md +342 -0
- package/src/presentation/hooks/useCreditsGate.md +346 -0
- package/src/presentation/hooks/useDeductCredit.md +176 -0
- package/src/presentation/hooks/useDevTestCallbacks.md +422 -0
- package/src/presentation/hooks/useFeatureGate.md +157 -0
- package/src/presentation/hooks/useInitializeCredits.md +458 -0
- package/src/presentation/hooks/usePaywall.md +334 -0
- package/src/presentation/hooks/usePaywallOperations.md +486 -0
- package/src/presentation/hooks/usePaywallVisibility.md +344 -0
- package/src/presentation/hooks/usePremium.md +230 -0
- package/src/presentation/hooks/usePremiumGate.md +423 -0
- package/src/presentation/hooks/usePremiumWithCredits.md +429 -0
- package/src/presentation/hooks/useSubscription.md +450 -0
- package/src/presentation/hooks/useSubscriptionDetails.md +438 -0
- package/src/presentation/hooks/useSubscriptionGate.md +168 -0
- package/src/presentation/hooks/useSubscriptionSettingsConfig.md +374 -0
- package/src/presentation/hooks/useSubscriptionStatus.md +424 -0
- package/src/presentation/hooks/useUserTier.md +356 -0
- package/src/presentation/hooks/useUserTierWithRepository.md +452 -0
- package/src/presentation/screens/README.md +194 -0
- package/src/presentation/types/README.md +38 -0
- package/src/presentation/utils/README.md +52 -0
- package/src/revenuecat/README.md +523 -0
- package/src/revenuecat/application/README.md +158 -0
- package/src/revenuecat/application/ports/README.md +169 -0
- package/src/revenuecat/domain/README.md +147 -0
- package/src/revenuecat/domain/constants/README.md +183 -0
- package/src/revenuecat/domain/entities/README.md +382 -0
- package/src/revenuecat/domain/errors/README.md +197 -0
- package/src/revenuecat/domain/types/README.md +373 -0
- package/src/revenuecat/domain/value-objects/README.md +441 -0
- package/src/revenuecat/infrastructure/README.md +50 -0
- package/src/revenuecat/infrastructure/config/README.md +40 -0
- package/src/revenuecat/infrastructure/handlers/README.md +218 -0
- package/src/revenuecat/infrastructure/managers/README.md +49 -0
- package/src/revenuecat/infrastructure/services/README.md +325 -0
- package/src/revenuecat/infrastructure/utils/README.md +382 -0
- package/src/revenuecat/presentation/README.md +184 -0
- package/src/revenuecat/presentation/hooks/README.md +56 -0
- package/src/utils/README.md +529 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Wallet Infrastructure
|
|
2
|
+
|
|
3
|
+
Infrastructure layer for wallet and credits functionality.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This directory contains implementations for credit persistence, transactions, and credit operations.
|
|
8
|
+
|
|
9
|
+
## Structure
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
infrastructure/
|
|
13
|
+
├── repositories/ # Data persistence implementations
|
|
14
|
+
│ └── CreditsRepository.ts
|
|
15
|
+
└── services/ # External service integrations
|
|
16
|
+
└── CreditsService.ts
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Credits Repository
|
|
20
|
+
|
|
21
|
+
Handles credit data persistence with duplicate protection.
|
|
22
|
+
|
|
23
|
+
### Key Features
|
|
24
|
+
|
|
25
|
+
- **Duplicate Protection**: Prevents duplicate credit allocations
|
|
26
|
+
- **Optimistic Updates**: Immediate UI updates with rollback
|
|
27
|
+
- **Transactional**: Atomic credit operations
|
|
28
|
+
- **ACCUMULATE Mode**: Adds credits on renewal
|
|
29
|
+
|
|
30
|
+
### Core Methods
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
class CreditsRepository implements ICreditsRepository {
|
|
34
|
+
async getCredits(userId: string): Promise<UserCredits | null>;
|
|
35
|
+
async initializeCredits(
|
|
36
|
+
userId: string,
|
|
37
|
+
purchaseId?: string,
|
|
38
|
+
productId?: string
|
|
39
|
+
): Promise<CreditsResult>;
|
|
40
|
+
async deductCredit(userId: string, amount: number): Promise<CreditsResult>;
|
|
41
|
+
async addCredits(userId: string, amount: number): Promise<CreditsResult>;
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Usage
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
import { CreditsRepository } from './repositories/CreditsRepository';
|
|
49
|
+
import { getCreditsRepository } from './repositories/CreditsRepositoryProvider';
|
|
50
|
+
|
|
51
|
+
const repository = getCreditsRepository();
|
|
52
|
+
|
|
53
|
+
// Initialize credits
|
|
54
|
+
const result = await repository.initializeCredits('user-123', 'purchase-456', 'premium_monthly');
|
|
55
|
+
if (result.success) {
|
|
56
|
+
console.log('Credits:', result.data.credits);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Deduct credits
|
|
60
|
+
const deductResult = await repository.deductCredit('user-123', 5);
|
|
61
|
+
if (deductResult.success) {
|
|
62
|
+
console.log('Credits deducted successfully');
|
|
63
|
+
} else if (deductResult.error?.code === 'CREDITS_EXHAUSTED') {
|
|
64
|
+
console.log('Not enough credits');
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Duplicate Protection
|
|
69
|
+
|
|
70
|
+
The repository prevents duplicate credit allocations based on `purchaseId`:
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
// First call
|
|
74
|
+
await repository.initializeCredits(userId, 'renewal-123', 'premium');
|
|
75
|
+
// Returns: { success: true, data: { credits: 100 } }
|
|
76
|
+
|
|
77
|
+
// Second call with same purchaseId
|
|
78
|
+
await repository.initializeCredits(userId, 'renewal-123', 'premium');
|
|
79
|
+
// Returns: { success: true, duplicate: true, data: { credits: 100 } }
|
|
80
|
+
// Credits not added again
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Best Practices
|
|
84
|
+
|
|
85
|
+
1. **Check Before Deduct**: Always verify credit balance
|
|
86
|
+
2. **Handle Errors**: Catch and handle credit errors appropriately
|
|
87
|
+
3. **Use Transactions**: Ensure data consistency
|
|
88
|
+
4. **Log Operations**: Track credit operations for debugging
|
|
89
|
+
5. **Test Edge Cases**: Zero credits, max credits, duplicates
|
|
90
|
+
6. **Validate Input**: Validate all input parameters
|
|
91
|
+
|
|
92
|
+
## Related
|
|
93
|
+
|
|
94
|
+
- [Wallet Domain](../domain/README.md)
|
|
95
|
+
- [Credits Entity](../domain/entities/README.md)
|
|
96
|
+
- [useCredits Hook](../../presentation/hooks/useCredits.md)
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Balance Card Component
|
|
3
3
|
*
|
|
4
|
-
* Displays user's credit balance with
|
|
4
|
+
* Displays user's credit balance with solid background.
|
|
5
5
|
* Props-driven for full customization.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import React from "react";
|
|
9
9
|
import { View, StyleSheet } from "react-native";
|
|
10
|
-
|
|
10
|
+
|
|
11
11
|
import {
|
|
12
12
|
useAppDesignTokens,
|
|
13
13
|
AtomicText,
|
|
@@ -31,17 +31,11 @@ export const BalanceCard: React.FC<BalanceCardProps> = ({
|
|
|
31
31
|
iconName = "wallet",
|
|
32
32
|
}) => {
|
|
33
33
|
const tokens = useAppDesignTokens();
|
|
34
|
-
|
|
35
|
-
tokens.colors.primary,
|
|
36
|
-
tokens.colors.primaryDark || tokens.colors.primary,
|
|
37
|
-
] as const;
|
|
34
|
+
|
|
38
35
|
|
|
39
36
|
return (
|
|
40
|
-
<
|
|
41
|
-
|
|
42
|
-
start={{ x: 0, y: 0 }}
|
|
43
|
-
end={{ x: 1, y: 1 }}
|
|
44
|
-
style={styles.container}
|
|
37
|
+
<View
|
|
38
|
+
style={[styles.container, { backgroundColor: tokens.colors.primary }]}
|
|
45
39
|
>
|
|
46
40
|
<View style={styles.content}>
|
|
47
41
|
<View style={styles.textContainer}>
|
|
@@ -73,7 +67,7 @@ export const BalanceCard: React.FC<BalanceCardProps> = ({
|
|
|
73
67
|
<AtomicIcon name={iconName} size="xl" color="onPrimary" />
|
|
74
68
|
</View>
|
|
75
69
|
</View>
|
|
76
|
-
</
|
|
70
|
+
</View>
|
|
77
71
|
);
|
|
78
72
|
};
|
|
79
73
|
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
# Wallet Presentation Components
|
|
2
|
+
|
|
3
|
+
UI components for wallet and credit display.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This directory contains React Native components for displaying credits, transactions, and wallet information.
|
|
8
|
+
|
|
9
|
+
## Components
|
|
10
|
+
|
|
11
|
+
### CreditBalanceCard
|
|
12
|
+
Card displaying current credit balance.
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
interface CreditBalanceCardProps {
|
|
16
|
+
credits: number;
|
|
17
|
+
balance: number;
|
|
18
|
+
isLoading?: boolean;
|
|
19
|
+
onPress?: () => void;
|
|
20
|
+
onRefresh?: () => void;
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Usage:**
|
|
25
|
+
```typescript
|
|
26
|
+
<CreditBalanceCard
|
|
27
|
+
credits={50}
|
|
28
|
+
balance={0.50}
|
|
29
|
+
onPress={() => navigation.navigate('CreditHistory')}
|
|
30
|
+
onRefresh={() => refetch()}
|
|
31
|
+
/>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### TransactionItem
|
|
35
|
+
Individual transaction display component.
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
interface TransactionItemProps {
|
|
39
|
+
transaction: Transaction;
|
|
40
|
+
format?: 'full' | 'compact';
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**Usage:**
|
|
45
|
+
```typescript
|
|
46
|
+
<TransactionItem
|
|
47
|
+
transaction={{
|
|
48
|
+
id: 'tx_123',
|
|
49
|
+
amount: -5,
|
|
50
|
+
reason: 'AI Generation',
|
|
51
|
+
timestamp: new Date(),
|
|
52
|
+
type: 'deduction',
|
|
53
|
+
}}
|
|
54
|
+
format="full"
|
|
55
|
+
/>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### CreditProgressBar
|
|
59
|
+
Progress bar showing credit usage.
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
interface CreditProgressBarProps {
|
|
63
|
+
current: number;
|
|
64
|
+
total: number;
|
|
65
|
+
showLabel?: boolean;
|
|
66
|
+
showPercentage?: boolean;
|
|
67
|
+
height?: number;
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Usage:**
|
|
72
|
+
```typescript
|
|
73
|
+
<CreditProgressBar
|
|
74
|
+
current={50}
|
|
75
|
+
total={100}
|
|
76
|
+
showLabel={true}
|
|
77
|
+
showPercentage={true}
|
|
78
|
+
height={8}
|
|
79
|
+
/>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### CreditPackageCard
|
|
83
|
+
Display card for purchasable credit packages.
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
interface CreditPackageCardProps {
|
|
87
|
+
package: {
|
|
88
|
+
id: string;
|
|
89
|
+
amount: number;
|
|
90
|
+
price: number;
|
|
91
|
+
bonus?: number;
|
|
92
|
+
currency: string;
|
|
93
|
+
};
|
|
94
|
+
onPress: () => void;
|
|
95
|
+
highlight?: boolean;
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Usage:**
|
|
100
|
+
```typescript
|
|
101
|
+
<CreditPackageCard
|
|
102
|
+
package={{
|
|
103
|
+
id: 'premium_credits',
|
|
104
|
+
amount: 100,
|
|
105
|
+
price: 9.99,
|
|
106
|
+
bonus: 10,
|
|
107
|
+
currency: 'USD',
|
|
108
|
+
}}
|
|
109
|
+
onPress={() => purchaseCredits('premium_credits')}
|
|
110
|
+
highlight={true}
|
|
111
|
+
/>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Usage Patterns
|
|
115
|
+
|
|
116
|
+
### Complete Credit Dashboard
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
function CreditDashboard() {
|
|
120
|
+
const { credits, transactions, isLoading, refetch } = useCredits();
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
<ScrollView>
|
|
124
|
+
<CreditBalanceCard
|
|
125
|
+
credits={credits}
|
|
126
|
+
balance={calculateBalance(credits)}
|
|
127
|
+
onRefresh={refetch}
|
|
128
|
+
/>
|
|
129
|
+
|
|
130
|
+
<View style={styles.section}>
|
|
131
|
+
<Text style={styles.title}>Recent Transactions</Text>
|
|
132
|
+
{transactions.slice(0, 10).map(tx => (
|
|
133
|
+
<TransactionItem key={tx.id} transaction={tx} />
|
|
134
|
+
))}
|
|
135
|
+
</View>
|
|
136
|
+
|
|
137
|
+
<Button onPress={() => navigation.navigate('CreditPackages')}>
|
|
138
|
+
Get More Credits
|
|
139
|
+
</Button>
|
|
140
|
+
</ScrollView>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Credit Warning Banner
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
function LowCreditWarning() {
|
|
149
|
+
const { credits } = useCredits();
|
|
150
|
+
const warningThreshold = 20;
|
|
151
|
+
|
|
152
|
+
if (credits > warningThreshold) return null;
|
|
153
|
+
|
|
154
|
+
return (
|
|
155
|
+
<Banner type={credits === 0 ? 'error' : 'warning'}>
|
|
156
|
+
<Text>
|
|
157
|
+
{credits === 0
|
|
158
|
+
? 'No credits remaining'
|
|
159
|
+
: `Only ${credits} credits left`
|
|
160
|
+
}
|
|
161
|
+
</Text>
|
|
162
|
+
<Button onPress={() => navigation.navigate('CreditPackages')}>
|
|
163
|
+
Get More
|
|
164
|
+
</Button>
|
|
165
|
+
</Banner>
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Credit History List
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
function TransactionHistory() {
|
|
174
|
+
const { transactions } = useCredits();
|
|
175
|
+
|
|
176
|
+
return (
|
|
177
|
+
<FlatList
|
|
178
|
+
data={transactions}
|
|
179
|
+
keyExtractor={(item) => item.id}
|
|
180
|
+
renderItem={({ item }) => (
|
|
181
|
+
<TransactionItem transaction={item} format="compact" />
|
|
182
|
+
)}
|
|
183
|
+
ListHeaderComponent={() => (
|
|
184
|
+
<Text style={styles.title}>Transaction History</Text>
|
|
185
|
+
)}
|
|
186
|
+
ListEmptyComponent={() => (
|
|
187
|
+
<EmptyState
|
|
188
|
+
icon="receipt"
|
|
189
|
+
title="No transactions yet"
|
|
190
|
+
message="Your credit transaction history will appear here"
|
|
191
|
+
/>
|
|
192
|
+
)}
|
|
193
|
+
/>
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Styling
|
|
199
|
+
|
|
200
|
+
Components use design system:
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
import { useAppDesignTokens } from '@umituz/react-native-design-system';
|
|
204
|
+
|
|
205
|
+
const tokens = useAppDesignTokens();
|
|
206
|
+
|
|
207
|
+
const styles = StyleSheet.create({
|
|
208
|
+
card: {
|
|
209
|
+
backgroundColor: tokens.colors.surface,
|
|
210
|
+
borderRadius: tokens.radius.lg,
|
|
211
|
+
padding: tokens.spacing.lg,
|
|
212
|
+
marginBottom: tokens.spacing.md,
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## Best Practices
|
|
218
|
+
|
|
219
|
+
1. **Visual Hierarchy**: Emphasize important information
|
|
220
|
+
2. **Color Coding**: Use colors to indicate status (low/warning/good)
|
|
221
|
+
3. **Loading States**: Show skeletons while loading
|
|
222
|
+
4. **Empty States**: Provide helpful empty state messages
|
|
223
|
+
5. **Refresh Capability**: Allow manual refresh
|
|
224
|
+
6. **Transactional History**: Show clear transaction history
|
|
225
|
+
7. **Purchase Links**: Easy access to purchase more credits
|
|
226
|
+
|
|
227
|
+
## Related
|
|
228
|
+
|
|
229
|
+
- [Credit Row](../../../presentation/components/details/CreditRow.md)
|
|
230
|
+
- [Credits Hook](../hooks/README.md)
|
|
231
|
+
- [Wallet Domain](../../domain/README.md)
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
# Wallet Presentation Hooks
|
|
2
|
+
|
|
3
|
+
React hooks for wallet and credit functionality.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This directory contains React hooks specifically for credit management and wallet operations.
|
|
8
|
+
|
|
9
|
+
## Hooks
|
|
10
|
+
|
|
11
|
+
### useCredits
|
|
12
|
+
Access and manage credit balance.
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
function useCredits(params: {
|
|
16
|
+
userId: string | undefined;
|
|
17
|
+
enabled?: boolean;
|
|
18
|
+
}): {
|
|
19
|
+
credits: number;
|
|
20
|
+
balance: number;
|
|
21
|
+
transactions: Transaction[];
|
|
22
|
+
isLoading: boolean;
|
|
23
|
+
error: Error | null;
|
|
24
|
+
refetch: () => Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Usage:**
|
|
29
|
+
```typescript
|
|
30
|
+
function CreditBalance() {
|
|
31
|
+
const { credits, isLoading } = useCredits({ userId: user?.uid });
|
|
32
|
+
|
|
33
|
+
if (isLoading) return <LoadingSpinner />;
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<View>
|
|
37
|
+
<Text>Balance: {credits} credits</Text>
|
|
38
|
+
</View>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### useDeductCredit
|
|
44
|
+
Deduct credits with optimistic updates.
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
function useDeductCredit(params: {
|
|
48
|
+
userId: string | undefined;
|
|
49
|
+
onCreditsExhausted?: () => void;
|
|
50
|
+
}): {
|
|
51
|
+
deductCredit: (cost?: number) => Promise<boolean>;
|
|
52
|
+
deductCredits: (cost: number) => Promise<boolean>;
|
|
53
|
+
isDeducting: boolean;
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Usage:**
|
|
58
|
+
```typescript
|
|
59
|
+
function AIGeneration() {
|
|
60
|
+
const { deductCredit } = useDeductCredit({
|
|
61
|
+
userId: user?.uid,
|
|
62
|
+
onCreditsExhausted: () => showPaywall(),
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const handleGenerate = async () => {
|
|
66
|
+
const success = await deductCredit(5);
|
|
67
|
+
if (success) {
|
|
68
|
+
await callAIGenerationAPI();
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### useInitializeCredits
|
|
75
|
+
Initialize credits after purchase.
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
function useInitializeCredits(params: {
|
|
79
|
+
userId: string | undefined;
|
|
80
|
+
}): {
|
|
81
|
+
initializeCredits: (options?: {
|
|
82
|
+
purchaseId?: string;
|
|
83
|
+
productId?: string;
|
|
84
|
+
}) => Promise<boolean>;
|
|
85
|
+
isInitializing: boolean;
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**Usage:**
|
|
90
|
+
```typescript
|
|
91
|
+
function PurchaseCompletion() {
|
|
92
|
+
const { initializeCredits } = useInitializeCredits({
|
|
93
|
+
userId: user?.uid,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const handlePurchaseComplete = async (transaction: Purchase) => {
|
|
97
|
+
const success = await initializeCredits({
|
|
98
|
+
purchaseId: transaction.transactionId,
|
|
99
|
+
productId: transaction.productId,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
if (success) {
|
|
103
|
+
Alert.alert('Success', 'Credits added!');
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### useCreditChecker
|
|
110
|
+
Check credit availability before operations.
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
function useCreditChecker(params?: {
|
|
114
|
+
onCreditDeducted?: (userId: string, cost: number) => void;
|
|
115
|
+
}): {
|
|
116
|
+
checkCreditsAvailable: (
|
|
117
|
+
userId: string | undefined,
|
|
118
|
+
cost?: number
|
|
119
|
+
) => Promise<CreditCheckResult>;
|
|
120
|
+
deductCreditsAfterSuccess: (
|
|
121
|
+
userId: string | undefined,
|
|
122
|
+
cost?: number
|
|
123
|
+
) => Promise<void>;
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**Usage:**
|
|
128
|
+
```typescript
|
|
129
|
+
function PreCheckFeature() {
|
|
130
|
+
const { checkCreditsAvailable } = useCreditChecker();
|
|
131
|
+
|
|
132
|
+
const handleCheck = async () => {
|
|
133
|
+
const result = await checkCreditsAvailable(user?.uid, 10);
|
|
134
|
+
|
|
135
|
+
if (!result.canProceed) {
|
|
136
|
+
Alert.alert(
|
|
137
|
+
'Insufficient Credits',
|
|
138
|
+
`You need ${result.required} credits but have ${result.currentBalance}`
|
|
139
|
+
);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
await executeFeature();
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Usage Patterns
|
|
149
|
+
|
|
150
|
+
### Two-Step Credit Process
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
function TwoStepCreditProcess() {
|
|
154
|
+
const { checkCreditsAvailable } = useCreditChecker();
|
|
155
|
+
const { deductCreditsAfterSuccess } = useCreditChecker();
|
|
156
|
+
|
|
157
|
+
const handleOperation = async () => {
|
|
158
|
+
// Step 1: Check credits
|
|
159
|
+
const check = await checkCreditsAvailable(user?.uid, 10);
|
|
160
|
+
|
|
161
|
+
if (!check.canProceed) {
|
|
162
|
+
showPaywall({ required: 10 });
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
// Step 2: Perform operation
|
|
168
|
+
await performOperation();
|
|
169
|
+
|
|
170
|
+
// Step 3: Deduct credits after success
|
|
171
|
+
await deductCreditsAfterSuccess(user?.uid, 10);
|
|
172
|
+
|
|
173
|
+
Alert.alert('Success', 'Operation completed');
|
|
174
|
+
} catch (error) {
|
|
175
|
+
Alert.alert('Error', 'Operation failed');
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### With Loading and Error States
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
function CreditOperation() {
|
|
185
|
+
const { credits, isLoading, error } = useCredits({ userId: user?.uid });
|
|
186
|
+
const { deductCredit } = useDeductCredit({
|
|
187
|
+
userId: user?.uid,
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
const handleDeduct = async () => {
|
|
191
|
+
if (isLoading) return;
|
|
192
|
+
|
|
193
|
+
const success = await deductCredit(5);
|
|
194
|
+
|
|
195
|
+
if (success) {
|
|
196
|
+
await executeFeature();
|
|
197
|
+
} else {
|
|
198
|
+
Alert.alert('Failed', 'Could not deduct credits');
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
return (
|
|
203
|
+
<View>
|
|
204
|
+
{error && <ErrorBanner message={error.message} />}
|
|
205
|
+
{isLoading ? (
|
|
206
|
+
<LoadingSpinner />
|
|
207
|
+
) : (
|
|
208
|
+
<>
|
|
209
|
+
<Text>Credits: {credits}</Text>
|
|
210
|
+
<Button onPress={handleDeduct} disabled={isLoading}>
|
|
211
|
+
Use 5 Credits
|
|
212
|
+
</Button>
|
|
213
|
+
</>
|
|
214
|
+
)}
|
|
215
|
+
</View>
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### With Auto-Initialization
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
function AutoInitialize() {
|
|
224
|
+
const { isPremium } = usePremium();
|
|
225
|
+
const { credits } = useCredits({ userId: user?.uid });
|
|
226
|
+
const { initializeCredits, isInitializing } = useInitializeCredits({
|
|
227
|
+
userId: user?.uid,
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
useEffect(() => {
|
|
231
|
+
// Auto-initialize if premium but no credits
|
|
232
|
+
if (isPremium && !credits && !isInitializing) {
|
|
233
|
+
initializeCredits();
|
|
234
|
+
}
|
|
235
|
+
}, [isPremium, credits]);
|
|
236
|
+
|
|
237
|
+
return <YourComponent />;
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## Best Practices
|
|
242
|
+
|
|
243
|
+
1. **Check Balance First**: Always check before deducting
|
|
244
|
+
2. **Handle Loading**: Show loading states appropriately
|
|
245
|
+
3. **Error Recovery**: Handle deduction failures gracefully
|
|
246
|
+
4. **Optimistic Updates**: Use optimistic updates for better UX
|
|
247
|
+
5. **Track Usage**: Log all credit operations
|
|
248
|
+
6. **Auto-Init**: Initialize credits after premium purchase
|
|
249
|
+
7. **Test Scenarios**: Test zero credits, max credits, failures
|
|
250
|
+
|
|
251
|
+
## Related
|
|
252
|
+
|
|
253
|
+
- [useCredits Hook Documentation](../../../presentation/hooks/useCredits.md)
|
|
254
|
+
- [useDeductCredit Hook Documentation](../../../presentation/hooks/useDeductCredit.md)
|
|
255
|
+
- [Credits Entity](../../domain/entities/README.md)
|