@umituz/react-native-subscription 2.14.96 → 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.
Files changed (77) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +462 -0
  3. package/package.json +3 -3
  4. package/src/application/README.md +229 -0
  5. package/src/application/ports/README.md +103 -0
  6. package/src/domain/README.md +402 -0
  7. package/src/domain/constants/README.md +80 -0
  8. package/src/domain/entities/README.md +176 -0
  9. package/src/domain/errors/README.md +307 -0
  10. package/src/domain/value-objects/README.md +186 -0
  11. package/src/domains/config/README.md +390 -0
  12. package/src/domains/paywall/README.md +371 -0
  13. package/src/domains/paywall/components/PaywallHeader.tsx +8 -11
  14. package/src/domains/paywall/components/README.md +185 -0
  15. package/src/domains/paywall/entities/README.md +199 -0
  16. package/src/domains/paywall/hooks/README.md +129 -0
  17. package/src/domains/wallet/README.md +292 -0
  18. package/src/domains/wallet/domain/README.md +108 -0
  19. package/src/domains/wallet/domain/entities/README.md +122 -0
  20. package/src/domains/wallet/domain/errors/README.md +157 -0
  21. package/src/domains/wallet/infrastructure/README.md +96 -0
  22. package/src/domains/wallet/presentation/components/BalanceCard.tsx +6 -12
  23. package/src/domains/wallet/presentation/components/README.md +231 -0
  24. package/src/domains/wallet/presentation/hooks/README.md +255 -0
  25. package/src/infrastructure/README.md +514 -0
  26. package/src/infrastructure/mappers/README.md +34 -0
  27. package/src/infrastructure/models/README.md +26 -0
  28. package/src/infrastructure/repositories/README.md +385 -0
  29. package/src/infrastructure/services/README.md +374 -0
  30. package/src/presentation/README.md +410 -0
  31. package/src/presentation/components/README.md +183 -0
  32. package/src/presentation/components/details/CreditRow.md +337 -0
  33. package/src/presentation/components/details/DetailRow.md +283 -0
  34. package/src/presentation/components/details/PremiumDetailsCard.md +266 -0
  35. package/src/presentation/components/details/PremiumStatusBadge.md +266 -0
  36. package/src/presentation/components/details/README.md +449 -0
  37. package/src/presentation/components/feedback/PaywallFeedbackModal.md +314 -0
  38. package/src/presentation/components/feedback/README.md +447 -0
  39. package/src/presentation/components/paywall/PaywallModal.md +444 -0
  40. package/src/presentation/components/paywall/README.md +190 -0
  41. package/src/presentation/components/sections/README.md +468 -0
  42. package/src/presentation/components/sections/SubscriptionSection.md +246 -0
  43. package/src/presentation/hooks/README.md +743 -0
  44. package/src/presentation/hooks/useAuthAwarePurchase.md +359 -0
  45. package/src/presentation/hooks/useAuthGate.md +403 -0
  46. package/src/presentation/hooks/useAuthSubscriptionSync.md +398 -0
  47. package/src/presentation/hooks/useCreditChecker.md +407 -0
  48. package/src/presentation/hooks/useCredits.md +342 -0
  49. package/src/presentation/hooks/useCreditsGate.md +346 -0
  50. package/src/presentation/hooks/useDeductCredit.md +160 -0
  51. package/src/presentation/hooks/useDevTestCallbacks.md +422 -0
  52. package/src/presentation/hooks/useFeatureGate.md +157 -0
  53. package/src/presentation/hooks/useInitializeCredits.md +458 -0
  54. package/src/presentation/hooks/usePaywall.md +334 -0
  55. package/src/presentation/hooks/usePaywallOperations.md +486 -0
  56. package/src/presentation/hooks/usePaywallVisibility.md +344 -0
  57. package/src/presentation/hooks/usePremium.md +230 -0
  58. package/src/presentation/hooks/usePremiumGate.md +423 -0
  59. package/src/presentation/hooks/usePremiumWithCredits.md +429 -0
  60. package/src/presentation/hooks/useSubscription.md +450 -0
  61. package/src/presentation/hooks/useSubscriptionDetails.md +438 -0
  62. package/src/presentation/hooks/useSubscriptionGate.md +168 -0
  63. package/src/presentation/hooks/useSubscriptionSettingsConfig.md +374 -0
  64. package/src/presentation/hooks/useSubscriptionStatus.md +424 -0
  65. package/src/presentation/hooks/useUserTier.md +356 -0
  66. package/src/presentation/hooks/useUserTierWithRepository.md +452 -0
  67. package/src/presentation/screens/README.md +194 -0
  68. package/src/presentation/types/README.md +38 -0
  69. package/src/presentation/utils/README.md +52 -0
  70. package/src/revenuecat/README.md +523 -0
  71. package/src/revenuecat/domain/README.md +147 -0
  72. package/src/revenuecat/domain/errors/README.md +197 -0
  73. package/src/revenuecat/infrastructure/config/README.md +40 -0
  74. package/src/revenuecat/infrastructure/managers/README.md +49 -0
  75. package/src/revenuecat/presentation/hooks/README.md +56 -0
  76. package/src/revenuecat/presentation/hooks/usePurchasePackage.ts +1 -1
  77. package/src/utils/README.md +529 -0
@@ -0,0 +1,402 @@
1
+ # Domain Layer
2
+
3
+ Abonelik sisteminin temel domain logic'ini, entity'lerini ve value object'lerini içeren katman.
4
+
5
+ ## Sorumluluklar
6
+
7
+ - **Entities**: Domain entity'lerini tanımlama
8
+ - **Value Objects**: Değer objelerini oluşturma
9
+ - **Domain Errors**: Domain-specific hataları tanımlama
10
+ - **Business Rules**: İş kurallarını encapsulate etme
11
+
12
+ ## Yapı
13
+
14
+ ```
15
+ domain/
16
+ ├── entities/
17
+ │ └── SubscriptionStatus.ts # Abonelik durumu entity'si
18
+ ├── value-objects/
19
+ │ └── SubscriptionConfig.ts # Konfigürasyon value object
20
+ └── errors/
21
+ ├── SubscriptionError.ts # Abonelik hataları
22
+ └── InsufficientCreditsError.ts # Kredi yetersizlik hatası
23
+ ```
24
+
25
+ ## Entities
26
+
27
+ ### SubscriptionStatus
28
+
29
+ Kullanıcının abonelik durumunu temsil eden ana entity:
30
+
31
+ ```typescript
32
+ import {
33
+ SubscriptionStatus,
34
+ SubscriptionStatusType,
35
+ createDefaultSubscriptionStatus,
36
+ isSubscriptionValid,
37
+ } from '@umituz/react-native-subscription';
38
+
39
+ // Varsayılan durum oluştur
40
+ const defaultStatus = createDefaultSubscriptionStatus();
41
+ // {
42
+ // type: 'unknown',
43
+ // isActive: false,
44
+ // isPremium: false,
45
+ // expirationDate: null,
46
+ // willRenew: false,
47
+ // }
48
+
49
+ // Premium durumu oluştur
50
+ const premiumStatus: SubscriptionStatus = {
51
+ type: 'premium',
52
+ isActive: true,
53
+ isPremium: true,
54
+ expirationDate: '2025-12-31T23:59:59Z',
55
+ willRenew: true,
56
+ productId: 'com.app.premium.annual',
57
+ };
58
+
59
+ // Abonelik geçerliliğini kontrol et
60
+ const isValid = isSubscriptionValid(premiumStatus); // true
61
+ ```
62
+
63
+ **SubscriptionStatusType:**
64
+
65
+ ```typescript
66
+ type SubscriptionStatusType =
67
+ | 'unknown' // Durum bilinmiyor
68
+ | 'guest' // Misafir kullanıcı
69
+ | 'free' // Free kullanıcı
70
+ | 'premium'; // Premium kullanıcı
71
+ ```
72
+
73
+ ## Value Objects
74
+
75
+ ### SubscriptionConfig
76
+
77
+ Abonelik konfigürasyonu için value object:
78
+
79
+ ```typescript
80
+ import type { SubscriptionConfig } from '@umituz/react-native-subscription';
81
+
82
+ const config: SubscriptionConfig = {
83
+ revenueCatApiKey: 'your_api_key',
84
+ revenueCatEntitlementId: 'premium',
85
+
86
+ plans: {
87
+ monthly: monthlyPlan,
88
+ annual: annualPlan,
89
+ },
90
+
91
+ defaultPlan: 'monthly',
92
+
93
+ features: {
94
+ requireAuth: true,
95
+ allowRestore: true,
96
+ syncWithFirebase: true,
97
+ },
98
+
99
+ ui: {
100
+ showAnnualDiscount: true,
101
+ highlightPopularPlan: true,
102
+ },
103
+
104
+ // Optional callbacks
105
+ onStatusChanged: (userId, newStatus) => {
106
+ console.log(`Status changed for ${userId}:`, newStatus);
107
+ },
108
+
109
+ onError: (error) => {
110
+ console.error('Subscription error:', error);
111
+ },
112
+ };
113
+ ```
114
+
115
+ ## Domain Errors
116
+
117
+ ### SubscriptionError
118
+
119
+ Abonelik işlemleri için hata sınıfı:
120
+
121
+ ```typescript
122
+ import {
123
+ SubscriptionError,
124
+ SubscriptionValidationError,
125
+ SubscriptionRepositoryError,
126
+ } from '@umituz/react-native-subscription';
127
+
128
+ // Doğrulama hatası
129
+ throw new SubscriptionValidationError('Invalid user ID');
130
+
131
+ // Repository hatası
132
+ throw new SubscriptionRepositoryError('Database connection failed');
133
+
134
+ // Genel hata
135
+ throw new SubscriptionError('Subscription operation failed');
136
+ ```
137
+
138
+ ### InsufficientCreditsError
139
+
140
+ Kredi yetersizliği hatası:
141
+
142
+ ```typescript
143
+ import {
144
+ InsufficientCreditsError,
145
+ type CreditErrorContext,
146
+ } from '@umituz/react-native-subscription';
147
+
148
+ const context: CreditErrorContext = {
149
+ required: 10,
150
+ available: 5,
151
+ featureId: 'ai_generation',
152
+ currency: 'USD',
153
+ };
154
+
155
+ throw new InsufficientCreditsError(
156
+ 'Not enough credits',
157
+ context
158
+ );
159
+
160
+ // Hata yakalama
161
+ try {
162
+ await deductCredits(userId, 10);
163
+ } catch (error) {
164
+ if (error instanceof InsufficientCreditsError) {
165
+ console.log(`Required: ${error.context.required}`);
166
+ console.log(`Available: ${error.context.available}`);
167
+ // Paywall göster
168
+ showPaywall();
169
+ }
170
+ }
171
+ ```
172
+
173
+ ## Helper Functions
174
+
175
+ ### SubscriptionStatus Helpers
176
+
177
+ ```typescript
178
+ import {
179
+ createDefaultSubscriptionStatus,
180
+ isSubscriptionValid,
181
+ isSubscriptionActive,
182
+ isUserPremium,
183
+ getSubscriptionTier,
184
+ } from '@umituz/react-native-subscription';
185
+
186
+ // Varsayılan durum oluştur
187
+ const status = createDefaultSubscriptionStatus();
188
+
189
+ // Geçerlilik kontrolü
190
+ const isValid = isSubscriptionValid(status);
191
+
192
+ // Aktif kontrolü
193
+ const isActive = isSubscriptionActive(status);
194
+
195
+ // Premium kontrolü
196
+ const isPremium = isUserPremium(status);
197
+
198
+ // Tier bilgisi
199
+ const tier = getSubscriptionTier(status); // 'free', 'premium'
200
+ ```
201
+
202
+ ## Factory Functions
203
+
204
+ ### Entity Factory
205
+
206
+ ```typescript
207
+ import { SubscriptionStatus } from '@umituz/react-native-subscription';
208
+
209
+ // Factory method ile oluştur
210
+ const status = SubscriptionStatus.create({
211
+ type: 'premium',
212
+ isActive: true,
213
+ isPremium: true,
214
+ expirationDate: '2025-12-31T23:59:59Z',
215
+ willRenew: true,
216
+ });
217
+
218
+ // Validation içerir
219
+ try {
220
+ const invalid = SubscriptionStatus.create({
221
+ type: 'premium',
222
+ isActive: false, // Çelişki: premium ama aktif değil
223
+ isPremium: true,
224
+ });
225
+ } catch (error) {
226
+ console.error('Validation error:', error.message);
227
+ }
228
+ ```
229
+
230
+ ## Type Guards
231
+
232
+ Domain entities için type guard'lar:
233
+
234
+ ```typescript
235
+ import {
236
+ isSubscriptionStatus,
237
+ isValidSubscriptionStatus,
238
+ } from '@umituz/react-native-subscription';
239
+
240
+ // Type guard
241
+ const obj = { type: 'premium', isActive: true, isPremium: true };
242
+
243
+ if (isSubscriptionStatus(obj)) {
244
+ // obj artık SubscriptionStatus tipinde
245
+ console.log(obj.type); // Type-safe!
246
+ }
247
+
248
+ // Validasyon + type guard
249
+ if (isValidSubscriptionStatus(obj)) {
250
+ // Geçerli bir SubscriptionStatus
251
+ }
252
+ ```
253
+
254
+ ## Domain Rules
255
+
256
+ ### Business Rule Examples
257
+
258
+ ```typescript
259
+ import { SubscriptionStatus } from '@umituz/react-native-subscription';
260
+
261
+ class SubscriptionDomain {
262
+ // Kural: Premium kullanıcı her zaman aktif olmalı
263
+ static validatePremiumStatus(status: SubscriptionStatus): boolean {
264
+ if (status.isPremium && !status.isActive) {
265
+ throw new Error('Premium users must be active');
266
+ }
267
+ return true;
268
+ }
269
+
270
+ // Kural: Abonelik tarihi geçmişse aktif olmamalı
271
+ static validateExpiration(status: SubscriptionStatus): boolean {
272
+ if (status.expirationDate) {
273
+ const isExpired = new Date(status.expirationDate) < new Date();
274
+ if (isExpired && status.isActive) {
275
+ throw new Error('Expired subscription cannot be active');
276
+ }
277
+ }
278
+ return true;
279
+ }
280
+
281
+ // Kural: Lifetime aboneliklerin son kullanma tarihi olmamalı
282
+ static validateLifetime(status: SubscriptionStatus): boolean {
283
+ if (status.type === 'premium' && status.willRenew === false) {
284
+ if (status.expirationDate !== null) {
285
+ throw new Error('Lifetime subscriptions cannot have expiration date');
286
+ }
287
+ }
288
+ return true;
289
+ }
290
+ }
291
+ ```
292
+
293
+ ## Best Practices
294
+
295
+ 1. **Immutable Objects**: Entity'leri immutable olarak tasarlayın
296
+ 2. **Validation**: Entity creation'da validation yapın
297
+ 3. **Encapsulation**: Business logic'i entity içinde tutun
298
+ 4. **Type Safety**: Strong typing kullanın
299
+ 5. **Domain Events**: Önemli domain olaylarını event olarak yayınlayın
300
+ 6. **Error Handling**: Domain-specific hatalar tanımlayın
301
+
302
+ ## Örnek: Domain Service
303
+
304
+ ```typescript
305
+ import {
306
+ SubscriptionStatus,
307
+ SubscriptionError,
308
+ isSubscriptionValid,
309
+ } from '@umituz/react-native-subscription';
310
+
311
+ class SubscriptionDomainService {
312
+ // Abonelik aktifleştirme
313
+ activateSubscription(
314
+ currentStatus: SubscriptionStatus,
315
+ productId: string,
316
+ expiresAt: string
317
+ ): SubscriptionStatus {
318
+ // Business rule: Zaten aktifse hata
319
+ if (currentStatus.isActive) {
320
+ throw new SubscriptionError('Subscription is already active');
321
+ }
322
+
323
+ // Yeni durum oluştur
324
+ const newStatus: SubscriptionStatus = {
325
+ type: 'premium',
326
+ isActive: true,
327
+ isPremium: true,
328
+ expirationDate: expiresAt,
329
+ willRenew: productId.includes('monthly') || productId.includes('annual'),
330
+ productId,
331
+ };
332
+
333
+ return newStatus;
334
+ }
335
+
336
+ // Abonelik iptali
337
+ deactivateSubscription(
338
+ status: SubscriptionStatus
339
+ ): SubscriptionStatus {
340
+ if (!status.isActive) {
341
+ throw new SubscriptionError('Subscription is not active');
342
+ }
343
+
344
+ return {
345
+ ...status,
346
+ isActive: false,
347
+ willRenew: false,
348
+ };
349
+ }
350
+
351
+ // Abonelik yenileme
352
+ renewSubscription(
353
+ status: SubscriptionStatus,
354
+ newExpirationDate: string
355
+ ): SubscriptionStatus {
356
+ if (!status.willRenew) {
357
+ throw new SubscriptionError('Subscription is set to not renew');
358
+ }
359
+
360
+ return {
361
+ ...status,
362
+ expirationDate: newExpirationDate,
363
+ isActive: true,
364
+ };
365
+ }
366
+ }
367
+ ```
368
+
369
+ ## Testing
370
+
371
+ Domain entities test edilebilir olmalı:
372
+
373
+ ```typescript
374
+ import { SubscriptionStatus, isSubscriptionValid } from '@umituz/react-native-subscription';
375
+
376
+ describe('SubscriptionStatus', () => {
377
+ it('should create valid premium status', () => {
378
+ const status = SubscriptionStatus.create({
379
+ type: 'premium',
380
+ isActive: true,
381
+ isPremium: true,
382
+ expirationDate: '2025-12-31T23:59:59Z',
383
+ willRenew: true,
384
+ });
385
+
386
+ expect(status.type).toBe('premium');
387
+ expect(status.isPremium).toBe(true);
388
+ });
389
+
390
+ it('should validate expiration', () => {
391
+ const expired = SubscriptionStatus.create({
392
+ type: 'premium',
393
+ isActive: true,
394
+ isPremium: true,
395
+ expirationDate: '2020-01-01T00:00:00Z',
396
+ willRenew: true,
397
+ });
398
+
399
+ expect(isSubscriptionValid(expired)).toBe(false);
400
+ });
401
+ });
402
+ ```
@@ -0,0 +1,80 @@
1
+ # Domain Constants
2
+
3
+ Constants used throughout the domain layer.
4
+
5
+ ## Overview
6
+
7
+ This directory contains constant definitions for subscription tiers, package periods, error codes, and other domain values.
8
+
9
+ ## Constants
10
+
11
+ ### Subscription Tiers
12
+
13
+ ```typescript
14
+ export const SUBSCRIPTION_TIERS = {
15
+ GUEST: 'guest',
16
+ FREE: 'free',
17
+ PREMIUM: 'premium',
18
+ } as const;
19
+
20
+ export type SubscriptionTier = typeof SUBSCRIPTION_TIERS[keyof typeof SUBSCRIPTION_TIERS];
21
+ ```
22
+
23
+ ### Package Periods
24
+
25
+ ```typescript
26
+ export const PACKAGE_PERIODS = {
27
+ MONTHLY: 'monthly',
28
+ ANNUAL: 'annual',
29
+ LIFETIME: 'lifetime',
30
+ } as const;
31
+
32
+ export type PackagePeriod = typeof PACKAGE_PERIODS[keyof typeof PACKAGE_PERIODS];
33
+ ```
34
+
35
+ ### Error Codes
36
+
37
+ ```typescript
38
+ export const ERROR_CODES = {
39
+ CREDITS_EXHAUSTED: 'CREDITS_EXHAUSTED',
40
+ USER_NOT_AUTHENTICATED: 'USER_NOT_AUTHENTICATED',
41
+ SUBSCRIPTION_EXPIRED: 'SUBSCRIPTION_EXPIRED',
42
+ INVALID_PACKAGE: 'INVALID_PACKAGE',
43
+ PURCHASE_FAILED: 'PURCHASE_FAILED',
44
+ DUPLICATE_PURCHASE: 'DUPLICATE_PURCHASE',
45
+ } as const;
46
+ ```
47
+
48
+ ### Credit Limits
49
+
50
+ ```typescript
51
+ export const CREDIT_LIMITS = {
52
+ MAX_MONTHLY_CREDITS: 100,
53
+ MAX_BONUS_CREDITS: 1000,
54
+ MIN_CREDITS: 0,
55
+ } as const;
56
+ ```
57
+
58
+ ### Time Periods
59
+
60
+ ```typescript
61
+ export const TIME_PERIODS = {
62
+ CREDIT_RESET_DAYS: 30,
63
+ EXPIRATION_WARNING_DAYS: 7,
64
+ EXPIRATION_CRITICAL_DAYS: 3,
65
+ } as const;
66
+ ```
67
+
68
+ ## Usage
69
+
70
+ ```typescript
71
+ import { SUBSCRIPTION_TIERS, PACKAGE_PERIODS } from './constants';
72
+
73
+ const tier: SubscriptionTier = SUBSCRIPTION_TIERS.PREMIUM;
74
+ const period: PackagePeriod = PACKAGE_PERIODS.ANNUAL;
75
+ ```
76
+
77
+ ## Related
78
+
79
+ - [Domain README](../README.md)
80
+ - [Entities](../entities/README.md)
@@ -0,0 +1,176 @@
1
+ # Domain Entities
2
+
3
+ Core domain entities for subscription management.
4
+
5
+ ## Purpose
6
+
7
+ Domain entities represent the core business concepts and rules of the subscription system. They are framework-agnostic and contain only business logic.
8
+
9
+ ## Entities
10
+
11
+ ### SubscriptionStatus
12
+
13
+ Represents the subscription state of a user.
14
+
15
+ ```typescript
16
+ interface SubscriptionStatus {
17
+ type: SubscriptionStatusType;
18
+ isActive: boolean;
19
+ isPremium: boolean;
20
+ expirationDate: string | null;
21
+ willRenew: boolean;
22
+ productId?: string;
23
+ }
24
+
25
+ type SubscriptionStatusType =
26
+ | 'unknown'
27
+ | 'guest'
28
+ | 'free'
29
+ | 'premium';
30
+ ```
31
+
32
+ **Usage:**
33
+ ```typescript
34
+ import { SubscriptionStatus } from '@umituz/react-native-subscription/domain';
35
+
36
+ const status = SubscriptionStatus.create({
37
+ type: 'premium',
38
+ isActive: true,
39
+ isPremium: true,
40
+ expirationDate: '2025-12-31',
41
+ willRenew: true,
42
+ });
43
+ ```
44
+
45
+ ## Design Principles
46
+
47
+ ### 1. Self-Validation
48
+
49
+ Entities validate themselves on creation:
50
+
51
+ ```typescript
52
+ class SubscriptionStatus {
53
+ private constructor(data: SubscriptionStatusData) {
54
+ this.validate(data);
55
+ // ...
56
+ }
57
+
58
+ private validate(data: SubscriptionStatusData): void {
59
+ if (data.isPremium && !data.isActive) {
60
+ throw new ValidationError('Premium users must be active');
61
+ }
62
+ }
63
+ }
64
+ ```
65
+
66
+ ### 2. Immutable State
67
+
68
+ Entities cannot be modified after creation:
69
+
70
+ ```typescript
71
+ const status = SubscriptionStatus.create({...});
72
+ // status.isActive = false; // Error: Cannot assign to read-only property
73
+ ```
74
+
75
+ ### 3. Business Rules
76
+
77
+ Business logic is encapsulated in entities:
78
+
79
+ ```typescript
80
+ class SubscriptionStatus {
81
+ isExpired(): boolean {
82
+ if (!this.expirationDate) return false;
83
+ return new Date(this.expirationDate) < new Date();
84
+ }
85
+
86
+ requiresRenewal(): boolean {
87
+ return this.isPremium && this.expirationDate && this.willRenew;
88
+ }
89
+ }
90
+ ```
91
+
92
+ ## Creating Entities
93
+
94
+ ### Factory Method
95
+
96
+ ```typescript
97
+ const status = SubscriptionStatus.create({
98
+ type: 'premium',
99
+ isActive: true,
100
+ isPremium: true,
101
+ expirationDate: null,
102
+ willRenew: false,
103
+ });
104
+ ```
105
+
106
+ ### Validation
107
+
108
+ ```typescript
109
+ try {
110
+ const status = SubscriptionStatus.create({
111
+ type: 'premium',
112
+ isActive: false, // Invalid!
113
+ isPremium: true,
114
+ });
115
+ } catch (error) {
116
+ console.error('Validation failed:', error.message);
117
+ }
118
+ ```
119
+
120
+ ## Domain Services
121
+
122
+ Business operations that don't naturally fit in entities:
123
+
124
+ ```typescript
125
+ class SubscriptionDomainService {
126
+ canUpgrade(currentStatus: SubscriptionStatus): boolean {
127
+ return !currentStatus.isPremium;
128
+ }
129
+
130
+ calculateDaysUntilExpiry(status: SubscriptionStatus): number | null {
131
+ if (!status.expirationDate) return null;
132
+ const diff = new Date(status.expirationDate).getTime() - Date.now();
133
+ return Math.ceil(diff / (1000 * 60 * 60 * 24));
134
+ }
135
+ }
136
+ ```
137
+
138
+ ## Best Practices
139
+
140
+ 1. **Keep entities pure** - No framework dependencies
141
+ 2. **Validate invariants** - Ensure valid state
142
+ 3. **Use value objects** - For complex attributes
143
+ 4. **Encapsulate logic** - Keep business rules inside entities
144
+ 5. **Make immutable** - Prevent direct state modification
145
+
146
+ ## Testing
147
+
148
+ ```typescript
149
+ describe('SubscriptionStatus', () => {
150
+ it('should create valid premium status', () => {
151
+ const status = SubscriptionStatus.create({
152
+ type: 'premium',
153
+ isActive: true,
154
+ isPremium: true,
155
+ });
156
+
157
+ expect(status.isPremium).toBe(true);
158
+ });
159
+
160
+ it('should reject invalid premium status', () => {
161
+ expect(() => {
162
+ SubscriptionStatus.create({
163
+ type: 'premium',
164
+ isActive: false,
165
+ isPremium: true,
166
+ });
167
+ }).toThrow();
168
+ });
169
+ });
170
+ ```
171
+
172
+ ## Related
173
+
174
+ - [Value Objects](../value-objects/README.md)
175
+ - [Domain Errors](../errors/README.md)
176
+ - [Domain Layer](../../README.md)