@umituz/react-native-subscription 2.14.97 → 2.14.98
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 +462 -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/config/README.md +390 -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 +160 -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/domain/README.md +147 -0
- package/src/revenuecat/domain/errors/README.md +197 -0
- package/src/revenuecat/infrastructure/config/README.md +40 -0
- package/src/revenuecat/infrastructure/managers/README.md +49 -0
- package/src/revenuecat/presentation/hooks/README.md +56 -0
- package/src/utils/README.md +529 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# useFeatureGate Hook
|
|
2
|
+
|
|
3
|
+
Unified feature gate combining authentication, subscription, and credits checks.
|
|
4
|
+
|
|
5
|
+
## Import
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { useFeatureGate } from '@umituz/react-native-subscription';
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Signature
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
function useFeatureGate(params: {
|
|
15
|
+
isAuthenticated: boolean;
|
|
16
|
+
onShowAuthModal: (pendingCallback: () => void | Promise<void>) => void;
|
|
17
|
+
hasSubscription?: boolean;
|
|
18
|
+
hasCredits: boolean;
|
|
19
|
+
creditBalance: number;
|
|
20
|
+
requiredCredits?: number;
|
|
21
|
+
onShowPaywall: (requiredCredits?: number) => void;
|
|
22
|
+
}): {
|
|
23
|
+
requireFeature: (action: () => void | Promise<void>) => void;
|
|
24
|
+
isAuthenticated: boolean;
|
|
25
|
+
hasSubscription: boolean;
|
|
26
|
+
hasCredits: boolean;
|
|
27
|
+
creditBalance: number;
|
|
28
|
+
canAccess: boolean;
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Parameters
|
|
33
|
+
|
|
34
|
+
| Parameter | Type | Default | Description |
|
|
35
|
+
|-----------|------|---------|-------------|
|
|
36
|
+
| `isAuthenticated` | `boolean` | **Required** | Whether user is authenticated |
|
|
37
|
+
| `onShowAuthModal` | `(callback) => void` | **Required** | Show auth modal with pending action |
|
|
38
|
+
| `hasSubscription` | `boolean` | `false` | Whether user has subscription |
|
|
39
|
+
| `hasCredits` | `boolean` | **Required** | Whether user has enough credits |
|
|
40
|
+
| `creditBalance` | `number` | **Required** | Current credit balance |
|
|
41
|
+
| `requiredCredits` | `number` | `undefined` | Credits required for action |
|
|
42
|
+
| `onShowPaywall` | `(credits?) => void` | **Required** | Show paywall/upgrade prompt |
|
|
43
|
+
|
|
44
|
+
## Returns
|
|
45
|
+
|
|
46
|
+
| Property | Type | Description |
|
|
47
|
+
|----------|------|-------------|
|
|
48
|
+
| `requireFeature` | `(action) => void` | Gate action behind auth, subscription, and credits |
|
|
49
|
+
| `isAuthenticated` | `boolean` | User is authenticated |
|
|
50
|
+
| `hasSubscription` | `boolean` | User has subscription |
|
|
51
|
+
| `hasCredits` | `boolean` | User has enough credits |
|
|
52
|
+
| `creditBalance` | `number` | Current credit balance |
|
|
53
|
+
| `canAccess` | `boolean` | User can access the feature |
|
|
54
|
+
|
|
55
|
+
## Basic Usage
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
function PremiumFeature() {
|
|
59
|
+
const { user } = useAuth();
|
|
60
|
+
const { isPremium } = usePremium();
|
|
61
|
+
const { credits } = useCredits();
|
|
62
|
+
|
|
63
|
+
const { requireFeature, canAccess } = useFeatureGate({
|
|
64
|
+
isAuthenticated: !!user,
|
|
65
|
+
onShowAuthModal: (pendingAction) => {
|
|
66
|
+
navigation.navigate('Auth', { pendingAction });
|
|
67
|
+
},
|
|
68
|
+
hasSubscription: isPremium,
|
|
69
|
+
hasCredits: credits >= 5,
|
|
70
|
+
creditBalance: credits,
|
|
71
|
+
requiredCredits: 5,
|
|
72
|
+
onShowPaywall: (required) => {
|
|
73
|
+
if (isPremium) {
|
|
74
|
+
showInsufficientCreditsModal(required);
|
|
75
|
+
} else {
|
|
76
|
+
showPaywall();
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const handleGenerate = () => {
|
|
82
|
+
requireFeature(async () => {
|
|
83
|
+
await generateContent();
|
|
84
|
+
});
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<Button
|
|
89
|
+
onPress={handleGenerate}
|
|
90
|
+
title={!canAccess ? 'Unlock Feature' : 'Generate'}
|
|
91
|
+
/>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Advanced Usage
|
|
97
|
+
|
|
98
|
+
### Progressive Access Control
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
function ProgressiveFeatureGate() {
|
|
102
|
+
const { user } = useAuth();
|
|
103
|
+
const { isPremium } = usePremium();
|
|
104
|
+
const { credits } = useCredits();
|
|
105
|
+
|
|
106
|
+
const { requireFeature, canAccess, isAuthenticated, hasSubscription, hasCredits } =
|
|
107
|
+
useFeatureGate({
|
|
108
|
+
isAuthenticated: !!user,
|
|
109
|
+
onShowAuthModal: (pending) => showAuthModal({ pendingAction: pending }),
|
|
110
|
+
hasSubscription: isPremium,
|
|
111
|
+
hasCredits: credits >= 3,
|
|
112
|
+
creditBalance: credits,
|
|
113
|
+
requiredCredits: 3,
|
|
114
|
+
onShowPaywall: (required) => showUpgradeModal({ requiredCredits: required }),
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const getAccessMessage = () => {
|
|
118
|
+
if (!isAuthenticated) return 'Sign in to access';
|
|
119
|
+
if (!hasSubscription && !hasCredits) return 'Upgrade or purchase credits';
|
|
120
|
+
if (!hasCredits) return 'Insufficient credits';
|
|
121
|
+
return 'Full access';
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<View>
|
|
126
|
+
<Text>{getAccessMessage()}</Text>
|
|
127
|
+
<Button
|
|
128
|
+
onPress={() => requireFeature(() => executeAction())}
|
|
129
|
+
disabled={!canAccess}
|
|
130
|
+
title={canAccess ? 'Execute Action' : 'Unlock'}
|
|
131
|
+
/>
|
|
132
|
+
</View>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Best Practices
|
|
138
|
+
|
|
139
|
+
1. **Check in order** - Auth → Subscription → Credits
|
|
140
|
+
2. **Clear messaging** - Tell users exactly what's required
|
|
141
|
+
3. **Preserve intent** - Queue pending actions through auth/purchase
|
|
142
|
+
4. **Smart prompts** - Show relevant upgrade option
|
|
143
|
+
5. **Track events** - Monitor gate triggers for optimization
|
|
144
|
+
6. **Respect premium** - Bypass credits for subscribers
|
|
145
|
+
7. **Show alternatives** - Offer both subscription and credits options
|
|
146
|
+
|
|
147
|
+
## Related Hooks
|
|
148
|
+
|
|
149
|
+
- **useAuthGate** - Auth gating only
|
|
150
|
+
- **useSubscriptionGate** - Subscription gating only
|
|
151
|
+
- **useCreditsGate** - Credits gating only
|
|
152
|
+
- **usePremiumGate** - Premium feature gating
|
|
153
|
+
|
|
154
|
+
## See Also
|
|
155
|
+
|
|
156
|
+
- [Feature Gating](../../../docs/FEATURE_GATING.md)
|
|
157
|
+
- [Access Control Patterns](../../../docs/ACCESS_PATTERNS.md)
|
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
# useInitializeCredits Hook
|
|
2
|
+
|
|
3
|
+
TanStack Query mutation hook for initializing credits after purchase.
|
|
4
|
+
|
|
5
|
+
## Import
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { useInitializeCredits } from '@umituz/react-native-subscription';
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Signature
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
function useInitializeCredits(params: {
|
|
15
|
+
userId: string | undefined;
|
|
16
|
+
}): {
|
|
17
|
+
initializeCredits: (options?: {
|
|
18
|
+
purchaseId?: string;
|
|
19
|
+
productId?: string;
|
|
20
|
+
}) => Promise<boolean>;
|
|
21
|
+
isInitializing: boolean;
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Parameters
|
|
26
|
+
|
|
27
|
+
| Parameter | Type | Default | Description |
|
|
28
|
+
|-----------|------|---------|-------------|
|
|
29
|
+
| `userId` | `string \| undefined` | **Required** | User ID for credit initialization |
|
|
30
|
+
|
|
31
|
+
## Returns
|
|
32
|
+
|
|
33
|
+
| Property | Type | Description |
|
|
34
|
+
|----------|------|-------------|
|
|
35
|
+
| `initializeCredits` | `(options?) => Promise<boolean>` | Initialize credits function |
|
|
36
|
+
| `isInitializing` | `boolean` | Mutation is in progress |
|
|
37
|
+
|
|
38
|
+
## Options
|
|
39
|
+
|
|
40
|
+
| Property | Type | Default | Description |
|
|
41
|
+
|-----------|------|---------|-------------|
|
|
42
|
+
| `purchaseId` | `string` | `undefined` | Optional purchase/renewal ID |
|
|
43
|
+
| `productId` | `string` | `undefined` | Optional product ID |
|
|
44
|
+
|
|
45
|
+
## Basic Usage
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
function CreditsInitializer() {
|
|
49
|
+
const { user } = useAuth();
|
|
50
|
+
|
|
51
|
+
const { initializeCredits, isInitializing } = useInitializeCredits({
|
|
52
|
+
userId: user?.uid,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const handleInitialize = async () => {
|
|
56
|
+
const success = await initializeCredits();
|
|
57
|
+
|
|
58
|
+
if (success) {
|
|
59
|
+
Alert.alert('Success', 'Credits initialized successfully');
|
|
60
|
+
} else {
|
|
61
|
+
Alert.alert('Error', 'Failed to initialize credits');
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<Button
|
|
67
|
+
onPress={handleInitialize}
|
|
68
|
+
disabled={isInitializing}
|
|
69
|
+
title={isInitializing ? 'Initializing...' : 'Initialize Credits'}
|
|
70
|
+
/>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Advanced Usage
|
|
76
|
+
|
|
77
|
+
### With Purchase ID
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
function PurchaseCompletion() {
|
|
81
|
+
const { user } = useAuth();
|
|
82
|
+
|
|
83
|
+
const { initializeCredits, isInitializing } = useInitializeCredits({
|
|
84
|
+
userId: user?.uid,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const handlePurchaseComplete = async (transaction: PurchaseTransaction) => {
|
|
88
|
+
const success = await initializeCredits({
|
|
89
|
+
purchaseId: transaction.transactionId,
|
|
90
|
+
productId: transaction.productId,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
if (success) {
|
|
94
|
+
analytics.track('credits_initialized', {
|
|
95
|
+
userId: user?.uid,
|
|
96
|
+
purchaseId: transaction.transactionId,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return success;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
return <PurchaseFlow onComplete={handlePurchaseComplete} />;
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### With Auto-Initialize for Premium
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
function PremiumUserSetup() {
|
|
111
|
+
const { user } = useAuth();
|
|
112
|
+
const { isPremium } = usePremium();
|
|
113
|
+
const { credits } = useCredits();
|
|
114
|
+
|
|
115
|
+
const { initializeCredits, isInitializing } = useInitializeCredits({
|
|
116
|
+
userId: user?.uid,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
// Auto-initialize credits for premium users who don't have them yet
|
|
121
|
+
if (isPremium && !credits && !isInitializing) {
|
|
122
|
+
const init = async () => {
|
|
123
|
+
const success = await initializeCredits();
|
|
124
|
+
if (success) {
|
|
125
|
+
console.log('Credits initialized for premium user');
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
init();
|
|
129
|
+
}
|
|
130
|
+
}, [isPremium, credits]);
|
|
131
|
+
|
|
132
|
+
return <YourAppContent />;
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### With Product-Specific Allocations
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
function ProductBasedAllocation() {
|
|
140
|
+
const { user } = useAuth();
|
|
141
|
+
|
|
142
|
+
const { initializeCredits } = useInitializeCredits({
|
|
143
|
+
userId: user?.uid,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const handlePurchase = async (productId: string) => {
|
|
147
|
+
// Different products provide different credit amounts
|
|
148
|
+
const productConfig = {
|
|
149
|
+
'premium_monthly': { credits: 100 },
|
|
150
|
+
'premium_yearly': { credits: 1200 },
|
|
151
|
+
'credits_small': { credits: 50 },
|
|
152
|
+
'credits_large': { credits: 500 },
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const config = productConfig[productId];
|
|
156
|
+
if (!config) {
|
|
157
|
+
Alert.alert('Error', 'Unknown product');
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const success = await initializeCredits({
|
|
162
|
+
purchaseId: `purchase_${Date.now()}`,
|
|
163
|
+
productId,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
if (success) {
|
|
167
|
+
Alert.alert('Success', `You received ${config.credits} credits!`);
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
return <PackageList onSelectPackage={handlePurchase} />;
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### With Error Handling
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
function RobustInitialization() {
|
|
179
|
+
const { user } = useAuth();
|
|
180
|
+
|
|
181
|
+
const {
|
|
182
|
+
initializeCredits,
|
|
183
|
+
isInitializing,
|
|
184
|
+
} = useInitializeCredits({
|
|
185
|
+
userId: user?.uid,
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
const handleInitialize = async () => {
|
|
189
|
+
if (!user?.uid) {
|
|
190
|
+
Alert.alert('Error', 'User not authenticated');
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
const success = await initializeCredits({
|
|
196
|
+
purchaseId: `init_${Date.now()}`,
|
|
197
|
+
productId: 'premium_subscription',
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
if (success) {
|
|
201
|
+
Alert.alert('Success', 'Credits initialized');
|
|
202
|
+
} else {
|
|
203
|
+
Alert.alert('Failed', 'Could not initialize credits');
|
|
204
|
+
}
|
|
205
|
+
} catch (error) {
|
|
206
|
+
console.error('Initialization error:', error);
|
|
207
|
+
Alert.alert(
|
|
208
|
+
'Error',
|
|
209
|
+
'Failed to initialize credits. Please try again.'
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
return (
|
|
215
|
+
<Button
|
|
216
|
+
onPress={handleInitialize}
|
|
217
|
+
disabled={isInitializing}
|
|
218
|
+
title="Initialize Credits"
|
|
219
|
+
/>
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### With Retry Logic
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
function InitializationWithRetry() {
|
|
228
|
+
const { user } = useAuth();
|
|
229
|
+
|
|
230
|
+
const { initializeCredits, isInitializing } = useInitializeCredits({
|
|
231
|
+
userId: user?.uid,
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
const handleInitializeWithRetry = async (retries = 3) => {
|
|
235
|
+
for (let i = 0; i < retries; i++) {
|
|
236
|
+
const success = await initializeCredits();
|
|
237
|
+
|
|
238
|
+
if (success) {
|
|
239
|
+
Alert.alert('Success', 'Credits initialized');
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Wait before retry
|
|
244
|
+
if (i < retries - 1) {
|
|
245
|
+
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
Alert.alert('Failed', 'Could not initialize credits after retries');
|
|
250
|
+
return false;
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
return (
|
|
254
|
+
<Button
|
|
255
|
+
onPress={() => handleInitializeWithRetry()}
|
|
256
|
+
disabled={isInitializing}
|
|
257
|
+
title="Initialize (with retry)"
|
|
258
|
+
/>
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## Examples
|
|
264
|
+
|
|
265
|
+
### Post-Purchase Flow
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
function PostPurchaseFlow() {
|
|
269
|
+
const { user } = useAuth();
|
|
270
|
+
const navigation = useNavigation();
|
|
271
|
+
|
|
272
|
+
const { initializeCredits, isInitializing } = useInitializeCredits({
|
|
273
|
+
userId: user?.uid,
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
useEffect(() => {
|
|
277
|
+
const handlePurchase = async (purchase: any) => {
|
|
278
|
+
console.log('Purchase completed:', purchase);
|
|
279
|
+
|
|
280
|
+
const success = await initializeCredits({
|
|
281
|
+
purchaseId: purchase.transactionId,
|
|
282
|
+
productId: purchase.productId,
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
if (success) {
|
|
286
|
+
// Navigate to success screen
|
|
287
|
+
navigation.replace('PurchaseSuccess', {
|
|
288
|
+
credits: purchase.credits,
|
|
289
|
+
});
|
|
290
|
+
} else {
|
|
291
|
+
// Show error
|
|
292
|
+
Alert.alert(
|
|
293
|
+
'Setup Required',
|
|
294
|
+
'Could not initialize credits. Please contact support.'
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
const subscription = purchasesEmitter.on('purchase_complete', handlePurchase);
|
|
300
|
+
|
|
301
|
+
return () => subscription.remove();
|
|
302
|
+
}, []);
|
|
303
|
+
|
|
304
|
+
if (isInitializing) {
|
|
305
|
+
return (
|
|
306
|
+
<View>
|
|
307
|
+
<ActivityIndicator size="large" />
|
|
308
|
+
<Text>Setting up your credits...</Text>
|
|
309
|
+
</View>
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return <YourAppContent />;
|
|
314
|
+
}
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### Subscription Renewal Handler
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
function RenewalHandler() {
|
|
321
|
+
const { user } = useAuth();
|
|
322
|
+
|
|
323
|
+
const { initializeCredits } = useInitializeCredits({
|
|
324
|
+
userId: user?.uid,
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
useEffect(() => {
|
|
328
|
+
const handleRenewal = async (renewalInfo: RenewalEvent) => {
|
|
329
|
+
console.log('Subscription renewed:', renewalInfo);
|
|
330
|
+
|
|
331
|
+
const success = await initializeCredits({
|
|
332
|
+
purchaseId: renewalInfo.renewalId,
|
|
333
|
+
productId: renewalInfo.productId,
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
if (success) {
|
|
337
|
+
analytics.track('subscription_renewed', {
|
|
338
|
+
userId: user?.uid,
|
|
339
|
+
renewalId: renewalInfo.renewalId,
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
// Notify user
|
|
343
|
+
showNotification('Credits Added!', 'Your subscription has renewed');
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
const subscription = subscriptionEmitter.on('renewal', handleRenewal);
|
|
348
|
+
|
|
349
|
+
return () => subscription.remove();
|
|
350
|
+
}, []);
|
|
351
|
+
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### Manual Admin Initialization
|
|
357
|
+
|
|
358
|
+
```typescript
|
|
359
|
+
function AdminCreditInitializer() {
|
|
360
|
+
const { user } = useAuth();
|
|
361
|
+
const { isAdmin } = useAdmin();
|
|
362
|
+
|
|
363
|
+
const { initializeCredits, isInitializing } = useInitializeCredits({
|
|
364
|
+
userId: targetUserId, // Admin can initialize for other users
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
const handleAdminInit = async () => {
|
|
368
|
+
if (!isAdmin) {
|
|
369
|
+
Alert.alert('Access Denied', 'Admin access required');
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
Alert.alert(
|
|
374
|
+
'Confirm Initialization',
|
|
375
|
+
'Initialize credits for this user?',
|
|
376
|
+
[
|
|
377
|
+
{ text: 'Cancel', style: 'cancel' },
|
|
378
|
+
{
|
|
379
|
+
text: 'Initialize',
|
|
380
|
+
onPress: async () => {
|
|
381
|
+
const success = await initializeCredits({
|
|
382
|
+
purchaseId: `admin_init_${Date.now()}`,
|
|
383
|
+
productId: 'admin_grant',
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
Alert.alert(
|
|
387
|
+
success ? 'Success' : 'Failed',
|
|
388
|
+
success ? 'Credits initialized' : 'Could not initialize credits'
|
|
389
|
+
);
|
|
390
|
+
},
|
|
391
|
+
},
|
|
392
|
+
]
|
|
393
|
+
);
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
if (!isAdmin) return null;
|
|
397
|
+
|
|
398
|
+
return (
|
|
399
|
+
<Button
|
|
400
|
+
onPress={handleAdminInit}
|
|
401
|
+
disabled={isInitializing}
|
|
402
|
+
title="Initialize Credits (Admin)"
|
|
403
|
+
/>
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
## Development Logging
|
|
409
|
+
|
|
410
|
+
In development mode, the hook logs useful information:
|
|
411
|
+
|
|
412
|
+
```typescript
|
|
413
|
+
// Initialization starts
|
|
414
|
+
[useInitializeCredits] Initializing: { userId: 'user-123', purchaseId: 'purchase-456', productId: 'premium_monthly' }
|
|
415
|
+
|
|
416
|
+
// Success
|
|
417
|
+
[useInitializeCredits] Success: { credits: 100, purchasedAt: '2024-01-15T10:30:00Z' }
|
|
418
|
+
|
|
419
|
+
// Error
|
|
420
|
+
[useInitializeCredits] Error: Some error message
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
## Best Practices
|
|
424
|
+
|
|
425
|
+
1. **Validate userId** - Always check user is authenticated
|
|
426
|
+
2. **Provide purchase info** - Include purchaseId and productId when possible
|
|
427
|
+
3. **Handle loading** - Show loading state during initialization
|
|
428
|
+
4. **Track success** - Log successful initializations
|
|
429
|
+
5. **Retry on failure** - Implement retry logic for reliability
|
|
430
|
+
6. **Use duplicate protection** - Repository handles duplicate purchase IDs
|
|
431
|
+
7. **Test scenarios** - Test new purchase, renewal, admin init
|
|
432
|
+
|
|
433
|
+
## Duplicate Protection
|
|
434
|
+
|
|
435
|
+
The repository prevents duplicate initialization with the same purchase ID:
|
|
436
|
+
|
|
437
|
+
```typescript
|
|
438
|
+
// First call
|
|
439
|
+
await initializeCredits({ purchaseId: 'renewal_123', productId: 'premium' });
|
|
440
|
+
// Returns: success, credits: 100 ✅
|
|
441
|
+
|
|
442
|
+
// Second call with same purchaseId
|
|
443
|
+
await initializeCredits({ purchaseId: 'renewal_123', productId: 'premium' });
|
|
444
|
+
// Returns: success, credits: 100 (same as before, not added again) ✅
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
## Related Hooks
|
|
448
|
+
|
|
449
|
+
- **useCredits** - For accessing credits balance
|
|
450
|
+
- **useDeductCredit** - For deducting credits
|
|
451
|
+
- **usePremiumWithCredits** - For premium + credits integration
|
|
452
|
+
- **useDevTestCallbacks** - For testing credit initialization
|
|
453
|
+
|
|
454
|
+
## See Also
|
|
455
|
+
|
|
456
|
+
- [Credits Repository](../../infrastructure/repositories/CreditsRepository.md)
|
|
457
|
+
- [Credits Entity](../../../domains/wallet/domain/entities/Credits.md)
|
|
458
|
+
- [Credits README](../../../domains/wallet/README.md)
|