@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,307 @@
|
|
|
1
|
+
# Domain Errors
|
|
2
|
+
|
|
3
|
+
Domain-specific error types for subscription system.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
Domain errors provide typed, contextual error handling for business logic failures. They make error handling explicit and type-safe.
|
|
8
|
+
|
|
9
|
+
## Error Hierarchy
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
Error (JavaScript)
|
|
13
|
+
└── SubscriptionError (Domain base)
|
|
14
|
+
├── SubscriptionValidationError
|
|
15
|
+
├── SubscriptionRepositoryError
|
|
16
|
+
├── SubscriptionOperationError
|
|
17
|
+
└── InsufficientCreditsError
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Error Types
|
|
21
|
+
|
|
22
|
+
### SubscriptionError
|
|
23
|
+
|
|
24
|
+
Base error for all subscription-related errors.
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
class SubscriptionError extends Error {
|
|
28
|
+
constructor(
|
|
29
|
+
message: string,
|
|
30
|
+
public readonly code: string,
|
|
31
|
+
public readonly context?: Record<string, any>
|
|
32
|
+
) {
|
|
33
|
+
super(message);
|
|
34
|
+
this.name = 'SubscriptionError';
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### SubscriptionValidationError
|
|
40
|
+
|
|
41
|
+
Thrown when validation fails.
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
throw new SubscriptionValidationError('Invalid user ID');
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### SubscriptionRepositoryError
|
|
48
|
+
|
|
49
|
+
Thrown when repository operations fail.
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
throw new SubscriptionRepositoryError('Database connection failed');
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### InsufficientCreditsError
|
|
56
|
+
|
|
57
|
+
Thrown when user doesn't have enough credits.
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
throw new InsufficientCreditsError(
|
|
61
|
+
'Not enough credits',
|
|
62
|
+
{
|
|
63
|
+
required: 10,
|
|
64
|
+
available: 5,
|
|
65
|
+
featureId: 'export',
|
|
66
|
+
}
|
|
67
|
+
);
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Usage
|
|
71
|
+
|
|
72
|
+
### Throwing Errors
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
function deductCredits(amount: number) {
|
|
76
|
+
if (amount <= 0) {
|
|
77
|
+
throw new SubscriptionValidationError(
|
|
78
|
+
'Amount must be positive',
|
|
79
|
+
'INVALID_AMOUNT',
|
|
80
|
+
{ amount }
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (currentBalance < amount) {
|
|
85
|
+
throw new InsufficientCreditsError(
|
|
86
|
+
'Insufficient credits',
|
|
87
|
+
{
|
|
88
|
+
required: amount,
|
|
89
|
+
available: currentBalance,
|
|
90
|
+
}
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Catching Errors
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
try {
|
|
100
|
+
await purchasePackage(packageToPurchase);
|
|
101
|
+
} catch (error) {
|
|
102
|
+
if (error instanceof InsufficientCreditsError) {
|
|
103
|
+
console.log(`Need ${error.context.required} credits`);
|
|
104
|
+
showPaywall();
|
|
105
|
+
} else if (error instanceof SubscriptionValidationError) {
|
|
106
|
+
console.error('Validation error:', error.message);
|
|
107
|
+
} else if (error instanceof SubscriptionError) {
|
|
108
|
+
console.error('Subscription error:', error.code, error.message);
|
|
109
|
+
} else {
|
|
110
|
+
console.error('Unexpected error:', error);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Type Guards
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
function isSubscriptionError(error: unknown): error is SubscriptionError {
|
|
119
|
+
return error instanceof SubscriptionError;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function isInsufficientCreditsError(error: unknown): error is InsufficientCreditsError {
|
|
123
|
+
return error instanceof InsufficientCreditsError;
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Error Codes
|
|
128
|
+
|
|
129
|
+
| Code | Description |
|
|
130
|
+
|------|-------------|
|
|
131
|
+
| `INVALID_USER_ID` | User ID is invalid or missing |
|
|
132
|
+
| `INVALID_PRODUCT_ID` | Product ID is not recognized |
|
|
133
|
+
| `INVALID_AMOUNT` | Amount is invalid (e.g., negative) |
|
|
134
|
+
| `INSUFFICIENT_CREDITS` | User doesn't have enough credits |
|
|
135
|
+
| `SUBSCRIPTION_EXPIRED` | Subscription has expired |
|
|
136
|
+
| `SUBSCRIPTION_NOT_FOUND` | Subscription doesn't exist |
|
|
137
|
+
| `REPOSITORY_ERROR` | Database/repository error |
|
|
138
|
+
| `NETWORK_ERROR` | Network connectivity issue |
|
|
139
|
+
| `PURCHASE_CANCELLED` | User cancelled purchase |
|
|
140
|
+
| `PURCHASE_FAILED` | Purchase operation failed |
|
|
141
|
+
|
|
142
|
+
## Error Context
|
|
143
|
+
|
|
144
|
+
Errors can include contextual information:
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
interface InsufficientCreditsContext {
|
|
148
|
+
required: number;
|
|
149
|
+
available: number;
|
|
150
|
+
featureId?: string;
|
|
151
|
+
currency?: string;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
throw new InsufficientCreditsError(
|
|
155
|
+
'Insufficient credits',
|
|
156
|
+
{
|
|
157
|
+
required: 10,
|
|
158
|
+
available: 5,
|
|
159
|
+
featureId: 'export',
|
|
160
|
+
currency: 'credits',
|
|
161
|
+
}
|
|
162
|
+
);
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Custom Errors
|
|
166
|
+
|
|
167
|
+
Create your own domain errors:
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
class FeatureAccessError extends SubscriptionError {
|
|
171
|
+
constructor(
|
|
172
|
+
featureId: string,
|
|
173
|
+
public readonly requiredTier: UserTier,
|
|
174
|
+
public readonly currentTier: UserTier
|
|
175
|
+
) {
|
|
176
|
+
super(
|
|
177
|
+
`Feature "${featureId}" requires ${requiredTier} tier`,
|
|
178
|
+
'FEATURE_ACCESS_DENIED',
|
|
179
|
+
{ featureId, requiredTier, currentTier }
|
|
180
|
+
);
|
|
181
|
+
this.name = 'FeatureAccessError';
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Usage
|
|
186
|
+
if (!canAccessFeature('advanced_analytics')) {
|
|
187
|
+
throw new FeatureAccessError(
|
|
188
|
+
'advanced_analytics',
|
|
189
|
+
'premium',
|
|
190
|
+
userTier
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Error Handling Patterns
|
|
196
|
+
|
|
197
|
+
### 1. Try-Catch with Type Guards
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
try {
|
|
201
|
+
await executeOperation();
|
|
202
|
+
} catch (error) {
|
|
203
|
+
if (error instanceof InsufficientCreditsError) {
|
|
204
|
+
handleInsufficientCredits(error);
|
|
205
|
+
} else if (error instanceof SubscriptionError) {
|
|
206
|
+
handleSubscriptionError(error);
|
|
207
|
+
} else {
|
|
208
|
+
handleUnexpectedError(error);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### 2. Result Type
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
type Result<T, E extends Error = Error> =
|
|
217
|
+
| { success: true; data: T }
|
|
218
|
+
| { success: false; error: E };
|
|
219
|
+
|
|
220
|
+
async function deductCredits(
|
|
221
|
+
amount: number
|
|
222
|
+
): Promise<Result<number, InsufficientCreditsError>> {
|
|
223
|
+
if (currentBalance < amount) {
|
|
224
|
+
return {
|
|
225
|
+
success: false,
|
|
226
|
+
error: new InsufficientCreditsError('...', {...}),
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return { success: true, data: newBalance };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Usage
|
|
234
|
+
const result = await deductCredits(10);
|
|
235
|
+
if (result.success) {
|
|
236
|
+
console.log('New balance:', result.data);
|
|
237
|
+
} else {
|
|
238
|
+
console.error('Error:', result.error.message);
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### 3. Error Boundary
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
class SubscriptionErrorBoundary extends React.Component {
|
|
246
|
+
state = { hasError: false, error: null };
|
|
247
|
+
|
|
248
|
+
static getDerivedStateFromError(error: Error) {
|
|
249
|
+
return { hasError: true, error };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
componentDidCatch(error: Error, errorInfo: any) {
|
|
253
|
+
if (error instanceof SubscriptionError) {
|
|
254
|
+
analytics().logEvent('subscription_error', {
|
|
255
|
+
code: error.code,
|
|
256
|
+
message: error.message,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
render() {
|
|
262
|
+
if (this.state.hasError) {
|
|
263
|
+
return <ErrorFallback error={this.state.error} />;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return this.props.children;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## Best Practices
|
|
272
|
+
|
|
273
|
+
1. **Use specific error types** - Don't use generic Error
|
|
274
|
+
2. **Include context** - Add relevant data to errors
|
|
275
|
+
3. **Document error codes** - List all possible errors
|
|
276
|
+
4. **Handle gracefully** - Show user-friendly messages
|
|
277
|
+
5. **Log errors** - Track for debugging
|
|
278
|
+
6. **Don't swallow errors** - Always handle or rethrow
|
|
279
|
+
7. **Use type guards** - Enable type-safe error handling
|
|
280
|
+
|
|
281
|
+
## Testing
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
describe('InsufficientCreditsError', () => {
|
|
285
|
+
it('should create error with context', () => {
|
|
286
|
+
const error = new InsufficientCreditsError('Not enough credits', {
|
|
287
|
+
required: 10,
|
|
288
|
+
available: 5,
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
expect(error.context.required).toBe(10);
|
|
292
|
+
expect(error.context.available).toBe(5);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it('should be instance of SubscriptionError', () => {
|
|
296
|
+
const error = new InsufficientCreditsError('...', {...});
|
|
297
|
+
|
|
298
|
+
expect(error).toBeInstanceOf(SubscriptionError);
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
## Related
|
|
304
|
+
|
|
305
|
+
- [Domain Entities](../entities/README.md)
|
|
306
|
+
- [Value Objects](../value-objects/README.md)
|
|
307
|
+
- [Domain Layer](../../README.md)
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# Domain Value Objects
|
|
2
|
+
|
|
3
|
+
Value objects for the subscription domain.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
Value objects are immutable objects that represent concepts by their attributes rather than identity. They are used to ensure validity and prevent primitive obsession.
|
|
8
|
+
|
|
9
|
+
## Value Objects
|
|
10
|
+
|
|
11
|
+
### SubscriptionConfig
|
|
12
|
+
|
|
13
|
+
Configuration object for subscription system.
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
interface SubscriptionConfig {
|
|
17
|
+
revenueCatApiKey: string;
|
|
18
|
+
revenueCatEntitlementId: string;
|
|
19
|
+
plans: Record<string, Plan>;
|
|
20
|
+
defaultPlan: string;
|
|
21
|
+
features: ConfigFeatures;
|
|
22
|
+
ui?: ConfigUI;
|
|
23
|
+
onStatusChanged?: (userId: string, status: SubscriptionStatus) => void;
|
|
24
|
+
onError?: (error: Error) => void;
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Usage:**
|
|
29
|
+
```typescript
|
|
30
|
+
import { SubscriptionConfig } from '@umituz/react-native-subscription/domain';
|
|
31
|
+
|
|
32
|
+
const config: SubscriptionConfig = {
|
|
33
|
+
revenueCatApiKey: 'your_key',
|
|
34
|
+
revenueCatEntitlementId: 'premium',
|
|
35
|
+
plans: {
|
|
36
|
+
monthly: monthlyPlan,
|
|
37
|
+
annual: annualPlan,
|
|
38
|
+
},
|
|
39
|
+
defaultPlan: 'monthly',
|
|
40
|
+
features: {
|
|
41
|
+
requireAuth: true,
|
|
42
|
+
allowRestore: true,
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Characteristics
|
|
48
|
+
|
|
49
|
+
### 1. Immutable
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
const config = SubscriptionConfig.create({...});
|
|
53
|
+
// config.apiKey = 'new_key'; // Error: Cannot assign
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 2. Value-Based Equality
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
const config1 = SubscriptionConfig.create({...});
|
|
60
|
+
const config2 = SubscriptionConfig.create({...});
|
|
61
|
+
|
|
62
|
+
config1.equals(config2); // true (if same values)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### 3. Self-Validating
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
const config = SubscriptionConfig.create({
|
|
69
|
+
apiKey: '', // Invalid!
|
|
70
|
+
entitlementId: 'premium',
|
|
71
|
+
});
|
|
72
|
+
// Throws ValidationError
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Creating Value Objects
|
|
76
|
+
|
|
77
|
+
### Factory Method
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
const config = SubscriptionConfig.create({
|
|
81
|
+
apiKey: 'your_key',
|
|
82
|
+
entitlementId: 'premium',
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Builder Pattern
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
const config = SubscriptionConfig.builder()
|
|
90
|
+
.apiKey('your_key')
|
|
91
|
+
.entitlementId('premium')
|
|
92
|
+
.addPlan('monthly', monthlyPlan)
|
|
93
|
+
.addPlan('annual', annualPlan)
|
|
94
|
+
.build();
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Common Value Objects
|
|
98
|
+
|
|
99
|
+
### Money
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
class Money {
|
|
103
|
+
private constructor(
|
|
104
|
+
private amount: number,
|
|
105
|
+
private currency: string
|
|
106
|
+
) {}
|
|
107
|
+
|
|
108
|
+
static create(amount: number, currency: string): Money {
|
|
109
|
+
if (amount < 0) throw new Error('Amount cannot be negative');
|
|
110
|
+
return new Money(amount, currency);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
format(): string {
|
|
114
|
+
return new Intl.NumberFormat('en-US', {
|
|
115
|
+
style: 'currency',
|
|
116
|
+
currency: this.currency,
|
|
117
|
+
}).format(this.amount);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
add(other: Money): Money {
|
|
121
|
+
if (this.currency !== other.currency) {
|
|
122
|
+
throw new Error('Cannot add different currencies');
|
|
123
|
+
}
|
|
124
|
+
return new Money(this.amount + other.amount, this.currency);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### DateRange
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
class DateRange {
|
|
133
|
+
constructor(
|
|
134
|
+
private start: Date,
|
|
135
|
+
private end: Date
|
|
136
|
+
) {}
|
|
137
|
+
|
|
138
|
+
includes(date: Date): boolean {
|
|
139
|
+
return date >= this.start && date <= this.end;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
durationInDays(): number {
|
|
143
|
+
return Math.ceil(
|
|
144
|
+
(this.end.getTime() - this.start.getTime()) / (1000 * 60 * 60 * 24)
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Best Practices
|
|
151
|
+
|
|
152
|
+
1. **Make immutable** - All properties readonly
|
|
153
|
+
2. **Validate on creation** - Fail fast
|
|
154
|
+
3. **Override equality** - Compare by value, not reference
|
|
155
|
+
4. **Use for complex attributes** - Don't use for simple primitives
|
|
156
|
+
5. **Keep small** - Value objects should be focused
|
|
157
|
+
|
|
158
|
+
## Testing
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
describe('SubscriptionConfig', () => {
|
|
162
|
+
it('should create valid config', () => {
|
|
163
|
+
const config = SubscriptionConfig.create({
|
|
164
|
+
apiKey: 'test_key',
|
|
165
|
+
entitlementId: 'premium',
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
expect(config.apiKey).toBe('test_key');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('should reject invalid config', () => {
|
|
172
|
+
expect(() => {
|
|
173
|
+
SubscriptionConfig.create({
|
|
174
|
+
apiKey: '',
|
|
175
|
+
entitlementId: 'premium',
|
|
176
|
+
});
|
|
177
|
+
}).toThrow();
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Related
|
|
183
|
+
|
|
184
|
+
- [Domain Entities](../entities/README.md)
|
|
185
|
+
- [Domain Errors](../errors/README.md)
|
|
186
|
+
- [Domain Layer](../../README.md)
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
# Domains
|
|
2
|
+
|
|
3
|
+
This directory contains specialized domain modules that implement specific business logic and features.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The `domains` directory organizes business logic into focused, self-contained modules. Each domain follows Domain-Driven Design (DDD) principles and Clean Architecture patterns.
|
|
8
|
+
|
|
9
|
+
## Domain Modules
|
|
10
|
+
|
|
11
|
+
### Wallet Domain (`wallet/`)
|
|
12
|
+
|
|
13
|
+
Manages credits, transactions, and wallet operations.
|
|
14
|
+
|
|
15
|
+
**Key Features:**
|
|
16
|
+
- Credit balance tracking
|
|
17
|
+
- Transaction history
|
|
18
|
+
- Credit deduction and allocation
|
|
19
|
+
- Purchase initialization
|
|
20
|
+
- Duplicate protection
|
|
21
|
+
|
|
22
|
+
**Structure:**
|
|
23
|
+
```
|
|
24
|
+
wallet/
|
|
25
|
+
├── domain/ # Business logic and entities
|
|
26
|
+
│ ├── entities/ # UserCredits, Transaction, CreditPackage
|
|
27
|
+
│ ├── types/ # Type definitions
|
|
28
|
+
│ └── mappers/ # Data transformation
|
|
29
|
+
├── infrastructure/ # External integrations
|
|
30
|
+
│ ├── repositories/ # Data persistence
|
|
31
|
+
│ └── services/ # External services
|
|
32
|
+
└── presentation/ # UI layer
|
|
33
|
+
├── hooks/ # React hooks
|
|
34
|
+
├── components/ # React components
|
|
35
|
+
└── screens/ # Screen components
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**Key Entities:**
|
|
39
|
+
- `UserCredits`: Credit balance and allocations
|
|
40
|
+
- `Transaction`: Credit transaction records
|
|
41
|
+
- `CreditPackage`: Purchasable credit packages
|
|
42
|
+
|
|
43
|
+
**Allocation Modes:**
|
|
44
|
+
- **ACCUMULATE**: Add credits on renewal (default)
|
|
45
|
+
- **REPLACE**: Replace existing credits with new allocation
|
|
46
|
+
|
|
47
|
+
**Related:**
|
|
48
|
+
- [Wallet Domain README](./wallet/README.md)
|
|
49
|
+
- [Wallet Entities](./wallet/domain/entities/README.md)
|
|
50
|
+
- [Wallet Hooks](./wallet/presentation/hooks/README.md)
|
|
51
|
+
|
|
52
|
+
### Paywall Domain (`paywall/`)
|
|
53
|
+
|
|
54
|
+
Manages paywall display, triggers, and purchase flows.
|
|
55
|
+
|
|
56
|
+
**Key Features:**
|
|
57
|
+
- Paywall visibility management
|
|
58
|
+
- Trigger-based paywall display
|
|
59
|
+
- Package selection and comparison
|
|
60
|
+
- Purchase operations
|
|
61
|
+
- Feedback collection
|
|
62
|
+
|
|
63
|
+
**Structure:**
|
|
64
|
+
```
|
|
65
|
+
paywall/
|
|
66
|
+
├── domain/ # Business logic
|
|
67
|
+
│ └── entities/ # PaywallTrigger, PaywallConfig, PaywallState
|
|
68
|
+
├── components/ # UI components
|
|
69
|
+
│ ├── PaywallScreen/
|
|
70
|
+
│ ├── PackageCard/
|
|
71
|
+
│ └── FeatureComparison/
|
|
72
|
+
└── hooks/ # React hooks
|
|
73
|
+
└── usePaywallOperations/
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Triggers:**
|
|
77
|
+
- `premium_feature`: User attempts premium feature
|
|
78
|
+
- `credit_gate`: Insufficient credits
|
|
79
|
+
- `manual`: Manually triggered
|
|
80
|
+
- `onboarding_complete`: After onboarding
|
|
81
|
+
- `usage_limit`: Reached usage limit
|
|
82
|
+
|
|
83
|
+
**Related:**
|
|
84
|
+
- [Paywall Domain README](./paywall/README.md)
|
|
85
|
+
- [Paywall Entities](./paywall/entities/README.md)
|
|
86
|
+
- [Paywall Components](./paywall/components/README.md)
|
|
87
|
+
|
|
88
|
+
### Config Domain (`config/`)
|
|
89
|
+
|
|
90
|
+
Manages subscription and feature configuration.
|
|
91
|
+
|
|
92
|
+
**Key Features:**
|
|
93
|
+
- Package configuration
|
|
94
|
+
- Feature flags
|
|
95
|
+
- Subscription settings
|
|
96
|
+
- Paywall customization
|
|
97
|
+
|
|
98
|
+
**Structure:**
|
|
99
|
+
```
|
|
100
|
+
config/
|
|
101
|
+
├── domain/ # Configuration entities
|
|
102
|
+
│ ├── entities/ # Config entities
|
|
103
|
+
│ └── value-objects/ # Configuration value objects
|
|
104
|
+
└── utils/ # Configuration utilities
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**Related:**
|
|
108
|
+
- [Config Domain README](./config/README.md)
|
|
109
|
+
|
|
110
|
+
## Architecture Principles
|
|
111
|
+
|
|
112
|
+
### 1. Domain-Driven Design (DDD)
|
|
113
|
+
Each domain represents a distinct business capability with:
|
|
114
|
+
- Clear boundaries
|
|
115
|
+
- Ubiquitous language
|
|
116
|
+
- Domain entities
|
|
117
|
+
- Business rules
|
|
118
|
+
|
|
119
|
+
### 2. Clean Architecture
|
|
120
|
+
Each domain follows layered architecture:
|
|
121
|
+
- **Domain Layer**: Business logic, entities (pure)
|
|
122
|
+
- **Application Layer**: Use cases, orchestration
|
|
123
|
+
- **Infrastructure Layer**: External integrations (persistence, APIs)
|
|
124
|
+
- **Presentation Layer**: UI components, hooks
|
|
125
|
+
|
|
126
|
+
### 3. Independence
|
|
127
|
+
Domains are independent and can:
|
|
128
|
+
- Be developed in isolation
|
|
129
|
+
- Have their own data stores
|
|
130
|
+
- Be tested independently
|
|
131
|
+
- Scale independently
|
|
132
|
+
|
|
133
|
+
### 4. Communication
|
|
134
|
+
Domains communicate through:
|
|
135
|
+
- Well-defined interfaces
|
|
136
|
+
- Events (when needed)
|
|
137
|
+
- Shared kernels (when necessary)
|
|
138
|
+
|
|
139
|
+
## Usage Patterns
|
|
140
|
+
|
|
141
|
+
### Using Wallet Domain
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
import { useCredits } from './wallet/presentation/hooks';
|
|
145
|
+
import { CreditBalanceCard } from './wallet/presentation/components';
|
|
146
|
+
|
|
147
|
+
function MyComponent() {
|
|
148
|
+
const { credits, transactions } = useCredits({ userId: user?.uid });
|
|
149
|
+
|
|
150
|
+
return (
|
|
151
|
+
<CreditBalanceCard
|
|
152
|
+
credits={credits}
|
|
153
|
+
balance={calculateBalance(credits)}
|
|
154
|
+
/>
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Using Paywall Domain
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
import { usePaywallOperations } from './paywall/hooks';
|
|
163
|
+
|
|
164
|
+
function MyScreen() {
|
|
165
|
+
const { handlePurchase } = usePaywallOperations({
|
|
166
|
+
userId: user?.uid,
|
|
167
|
+
isAnonymous: false,
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const onBuyPress = () => {
|
|
171
|
+
handlePurchase(selectedPackage);
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Using Config Domain
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
import { getPackageConfiguration } from './config/utils';
|
|
180
|
+
|
|
181
|
+
const packages = getPackageConfiguration('premium');
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Adding New Domains
|
|
185
|
+
|
|
186
|
+
When adding a new domain:
|
|
187
|
+
|
|
188
|
+
1. **Create Domain Structure**
|
|
189
|
+
```bash
|
|
190
|
+
src/domains/your-domain/
|
|
191
|
+
├── domain/
|
|
192
|
+
│ ├── entities/
|
|
193
|
+
│ ├── value-objects/
|
|
194
|
+
│ └── types/
|
|
195
|
+
├── infrastructure/
|
|
196
|
+
│ ├── repositories/
|
|
197
|
+
│ └── services/
|
|
198
|
+
└── presentation/
|
|
199
|
+
├── hooks/
|
|
200
|
+
├── components/
|
|
201
|
+
└── screens/
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
2. **Define Entities**
|
|
205
|
+
- Create core business entities
|
|
206
|
+
- Define business rules
|
|
207
|
+
- Implement validations
|
|
208
|
+
|
|
209
|
+
3. **Implement Infrastructure**
|
|
210
|
+
- Create repositories for data persistence
|
|
211
|
+
- Implement external service integrations
|
|
212
|
+
- Add mappers for data transformation
|
|
213
|
+
|
|
214
|
+
4. **Create Presentation Layer**
|
|
215
|
+
- Implement React hooks
|
|
216
|
+
- Create UI components
|
|
217
|
+
- Build screens
|
|
218
|
+
|
|
219
|
+
5. **Add Documentation**
|
|
220
|
+
- Create README for domain
|
|
221
|
+
- Document entities
|
|
222
|
+
- Provide usage examples
|
|
223
|
+
|
|
224
|
+
## Best Practices
|
|
225
|
+
|
|
226
|
+
1. **Encapsulation**: Keep domain logic isolated
|
|
227
|
+
2. **Purity**: Domain layer should have no external dependencies
|
|
228
|
+
3. **Interfaces**: Depend on abstractions, not concretions
|
|
229
|
+
4. **Testing**: Each domain should be independently testable
|
|
230
|
+
5. **Documentation**: Document business rules and entities
|
|
231
|
+
6. **Type Safety**: Use TypeScript for all domain models
|
|
232
|
+
7. **Validation**: Validate at domain boundaries
|
|
233
|
+
8. **Error Handling**: Use domain-specific errors
|
|
234
|
+
|
|
235
|
+
## Related
|
|
236
|
+
|
|
237
|
+
- [Domain Layer README](../domain/README.md)
|
|
238
|
+
- [Application Layer README](../application/README.md)
|
|
239
|
+
- [Infrastructure Layer README](../infrastructure/README.md)
|
|
240
|
+
- [RevenueCat Integration README](../revenuecat/README.md)
|