@umituz/react-native-subscription 2.14.98 → 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/README.md CHANGED
@@ -28,7 +28,6 @@ yarn add @umituz/react-native-subscription
28
28
  "@tanstack/react-query": ">=5.0.0",
29
29
  "expo-constants": ">=16.0.0",
30
30
  "expo-image": ">=2.0.0",
31
- "expo-linear-gradient": ">=14.0.0",
32
31
  "firebase": ">=10.0.0",
33
32
  "react": ">=18.2.0",
34
33
  "react-native": ">=0.74.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-subscription",
3
- "version": "2.14.98",
3
+ "version": "2.14.99",
4
4
  "description": "Complete subscription management with RevenueCat, paywall UI, and credits system for React Native apps",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -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)
@@ -0,0 +1,390 @@
1
+ # Config Domain
2
+
3
+ Domain layer for configuration management.
4
+
5
+ ## Overview
6
+
7
+ This directory contains the business logic and domain models for subscription and feature configuration.
8
+
9
+ ## Structure
10
+
11
+ ```
12
+ domain/
13
+ ├── entities/ # Configuration entities
14
+ └── value-objects/ # Configuration value objects
15
+ ```
16
+
17
+ ## Entities
18
+
19
+ ### PackageConfig
20
+
21
+ Represents a subscription package configuration.
22
+
23
+ ```typescript
24
+ class PackageConfig {
25
+ readonly identifier: string;
26
+ readonly productId: string;
27
+ readonly period: PackagePeriod;
28
+ readonly price: Money;
29
+ readonly features: Feature[];
30
+ readonly credits?: number;
31
+ readonly metadata: PackageMetadata;
32
+
33
+ constructor(config: PackageConfigData) {
34
+ this.identifier = config.identifier;
35
+ this.productId = config.productId;
36
+ this.period = config.period;
37
+ this.price = new Money(config.price, config.currency);
38
+ this.features = config.features;
39
+ this.credits = config.credits;
40
+ this.metadata = config.metadata || {};
41
+ this.validate();
42
+ }
43
+
44
+ private validate(): void {
45
+ if (!this.identifier) {
46
+ throw new Error('Package identifier is required');
47
+ }
48
+ if (!this.productId) {
49
+ throw new Error('Product ID is required');
50
+ }
51
+ if (this.price.getAmount() < 0) {
52
+ throw new Error('Price cannot be negative');
53
+ }
54
+ }
55
+
56
+ isAnnual(): boolean {
57
+ return this.period === 'annual';
58
+ }
59
+
60
+ isMonthly(): boolean {
61
+ return this.period === 'monthly';
62
+ }
63
+
64
+ isLifetime(): boolean {
65
+ return this.period === 'lifetime';
66
+ }
67
+
68
+ hasCredits(): boolean {
69
+ return !!this.credits && this.credits > 0;
70
+ }
71
+
72
+ isRecommended(): boolean {
73
+ return this.metadata.recommended === true;
74
+ }
75
+
76
+ isHighlighted(): boolean {
77
+ return this.metadata.highlight === true;
78
+ }
79
+
80
+ getDiscountPercentage(): number | null {
81
+ return this.metadata.discount?.percentage ?? null;
82
+ }
83
+
84
+ getBadge(): string | null {
85
+ return this.metadata.badge ?? null;
86
+ }
87
+
88
+ getPerMonthPrice(): Money | null {
89
+ if (this.period === 'monthly') {
90
+ return this.price;
91
+ }
92
+ if (this.period === 'annual') {
93
+ return this.price.divide(12);
94
+ }
95
+ return null;
96
+ }
97
+ }
98
+ ```
99
+
100
+ ### FeatureConfig
101
+
102
+ Represents a feature configuration.
103
+
104
+ ```typescript
105
+ class FeatureConfig {
106
+ readonly id: string;
107
+ readonly name: string;
108
+ readonly description?: string;
109
+ readonly requiresPremium: boolean;
110
+ readonly requiresCredits: boolean;
111
+ readonly creditCost?: number;
112
+ readonly enabled: boolean;
113
+ readonly gateType: 'premium' | 'credits' | 'both';
114
+
115
+ constructor(config: FeatureConfigData) {
116
+ this.id = config.id;
117
+ this.name = config.name;
118
+ this.description = config.description;
119
+ this.requiresPremium = config.requiresPremium;
120
+ this.requiresCredits = config.requiresCredits ?? false;
121
+ this.creditCost = config.creditCost;
122
+ this.enabled = config.enabled ?? true;
123
+ this.gateType = config.gateType ?? this.inferGateType();
124
+ this.validate();
125
+ }
126
+
127
+ private inferGateType(): 'premium' | 'credits' | 'both' {
128
+ if (this.requiresPremium && this.requiresCredits) {
129
+ return 'both';
130
+ }
131
+ if (this.requiresPremium) {
132
+ return 'premium';
133
+ }
134
+ return 'credits';
135
+ }
136
+
137
+ private validate(): void {
138
+ if (!this.id) {
139
+ throw new Error('Feature ID is required');
140
+ }
141
+ if (this.requiresCredits && !this.creditCost) {
142
+ throw new Error('Credit cost required when requiresCredits is true');
143
+ }
144
+ if (this.creditCost && this.creditCost < 0) {
145
+ throw new Error('Credit cost cannot be negative');
146
+ }
147
+ }
148
+
149
+ isAccessible(userHasPremium: boolean, userCredits: number): boolean {
150
+ if (!this.enabled) {
151
+ return false;
152
+ }
153
+
154
+ if (this.gateType === 'premium') {
155
+ return userHasPremium;
156
+ }
157
+
158
+ if (this.gateType === 'credits') {
159
+ return userCredits >= (this.creditCost ?? 0);
160
+ }
161
+
162
+ if (this.gateType === 'both') {
163
+ return userHasPremium && userCredits >= (this.creditCost ?? 0);
164
+ }
165
+
166
+ return false;
167
+ }
168
+
169
+ getRequiredCredits(): number {
170
+ return this.creditCost ?? 0;
171
+ }
172
+ }
173
+ ```
174
+
175
+ ### PaywallConfig
176
+
177
+ Represents paywall configuration.
178
+
179
+ ```typescript
180
+ class PaywallConfig {
181
+ readonly id: string;
182
+ readonly title: string;
183
+ readonly subtitle?: string;
184
+ readonly features: string[];
185
+ readonly packages: PackageConfig[];
186
+ readonly highlightPackage?: string;
187
+ readonly style: PaywallStyle;
188
+ readonly triggers: PaywallTrigger[];
189
+
190
+ constructor(config: PaywallConfigData) {
191
+ this.id = config.id;
192
+ this.title = config.title;
193
+ this.subtitle = config.subtitle;
194
+ this.features = config.features;
195
+ this.packages = config.packages.map(p => new PackageConfig(p));
196
+ this.highlightPackage = config.highlightPackage;
197
+ this.style = config.style;
198
+ this.triggers = config.triggers ?? [];
199
+ this.validate();
200
+ }
201
+
202
+ private validate(): void {
203
+ if (!this.id) {
204
+ throw new Error('Paywall ID is required');
205
+ }
206
+ if (this.packages.length === 0) {
207
+ throw new Error('At least one package is required');
208
+ }
209
+ if (this.highlightPackage) {
210
+ const exists = this.packages.some(p => p.identifier === this.highlightPackage);
211
+ if (!exists) {
212
+ throw new Error('Highlight package not found');
213
+ }
214
+ }
215
+ }
216
+
217
+ getHighlightedPackage(): PackageConfig | null {
218
+ if (!this.highlightPackage) {
219
+ return this.packages.find(p => p.isHighlighted()) ?? null;
220
+ }
221
+ return this.packages.find(p => p.identifier === this.highlightPackage) ?? null;
222
+ }
223
+
224
+ getPackageByIdentifier(identifier: string): PackageConfig | null {
225
+ return this.packages.find(p => p.identifier === identifier) ?? null;
226
+ }
227
+
228
+ shouldTrigger(triggerType: string): boolean {
229
+ return this.triggers.some(t => t.type === triggerType);
230
+ }
231
+ }
232
+ ```
233
+
234
+ ## Value Objects
235
+
236
+ ### PackagePeriod
237
+
238
+ ```typescript
239
+ class PackagePeriod {
240
+ readonly value: 'monthly' | 'annual' | 'lifetime' | 'weekly';
241
+
242
+ constructor(value: string) {
243
+ const valid = ['monthly', 'annual', 'lifetime', 'weekly'];
244
+ if (!valid.includes(value)) {
245
+ throw new Error(`Invalid period: ${value}`);
246
+ }
247
+ this.value = value as PackagePeriod['value'];
248
+ }
249
+
250
+ isRecurring(): boolean {
251
+ return this.value === 'monthly' || this.value === 'annual' || this.value === 'weekly';
252
+ }
253
+
254
+ getDurationInMonths(): number | null {
255
+ switch (this.value) {
256
+ case 'monthly': return 1;
257
+ case 'annual': return 12;
258
+ case 'weekly': return 1 / 4;
259
+ case 'lifetime': return null;
260
+ }
261
+ }
262
+
263
+ toString(): string {
264
+ return this.value;
265
+ }
266
+ }
267
+ ```
268
+
269
+ ### Money
270
+
271
+ ```typescript
272
+ class Money {
273
+ readonly amount: number;
274
+ readonly currency: string;
275
+
276
+ constructor(amount: number, currency: string) {
277
+ if (amount < 0) {
278
+ throw new Error('Amount cannot be negative');
279
+ }
280
+ this.amount = amount;
281
+ this.currency = currency;
282
+ }
283
+
284
+ format(locale: string = 'en-US'): string {
285
+ return new Intl.NumberFormat(locale, {
286
+ style: 'currency',
287
+ currency: this.currency,
288
+ }).format(this.amount);
289
+ }
290
+
291
+ divide(factor: number): Money {
292
+ return new Money(this.amount / factor, this.currency);
293
+ }
294
+
295
+ multiply(factor: number): Money {
296
+ return new Money(this.amount * factor, this.currency);
297
+ }
298
+
299
+ equals(other: Money): boolean {
300
+ return this.amount === other.amount && this.currency === other.currency;
301
+ }
302
+ }
303
+ ```
304
+
305
+ ## Usage
306
+
307
+ ### Creating Package Configuration
308
+
309
+ ```typescript
310
+ import { PackageConfig } from './entities/PackageConfig';
311
+
312
+ const monthlyPackage = new PackageConfig({
313
+ identifier: 'premium_monthly',
314
+ productId: 'com.app.premium.monthly',
315
+ period: 'monthly',
316
+ price: 9.99,
317
+ currency: 'USD',
318
+ features: ['Unlimited Access', 'Ad-Free'],
319
+ credits: 100,
320
+ metadata: {
321
+ recommended: true,
322
+ badge: 'Most Popular',
323
+ },
324
+ });
325
+
326
+ console.log(monthlyPackage.isMonthly()); // true
327
+ console.log(monthlyPackage.getPerMonthPrice()?.format()); // '$9.99'
328
+ ```
329
+
330
+ ### Creating Feature Configuration
331
+
332
+ ```typescript
333
+ import { FeatureConfig } from './entities/FeatureConfig';
334
+
335
+ const aiFeature = new FeatureConfig({
336
+ id: 'ai_generation',
337
+ name: 'AI Generation',
338
+ description: 'Generate AI content',
339
+ requiresPremium: false,
340
+ requiresCredits: true,
341
+ creditCost: 5,
342
+ enabled: true,
343
+ gateType: 'credits',
344
+ });
345
+
346
+ console.log(aiFeature.isAccessible(false, 10)); // true
347
+ console.log(aiFeature.isAccessible(false, 3)); // false
348
+ console.log(aiFeature.getRequiredCredits()); // 5
349
+ ```
350
+
351
+ ### Creating Paywall Configuration
352
+
353
+ ```typescript
354
+ import { PaywallConfig } from './entities/PaywallConfig';
355
+
356
+ const paywall = new PaywallConfig({
357
+ id: 'premium_upgrade',
358
+ title: 'Upgrade to Premium',
359
+ subtitle: 'Get unlimited access',
360
+ features: ['Feature 1', 'Feature 2'],
361
+ packages: [monthlyPackage, annualPackage],
362
+ highlightPackage: 'premium_annual',
363
+ style: {
364
+ primaryColor: '#007AFF',
365
+ backgroundColor: '#FFFFFF',
366
+ },
367
+ triggers: [
368
+ { type: 'credit_gate', enabled: true },
369
+ { type: 'manual', enabled: true },
370
+ ],
371
+ });
372
+
373
+ const highlighted = paywall.getHighlightedPackage();
374
+ console.log(highlighted?.identifier); // 'premium_annual'
375
+ ```
376
+
377
+ ## Best Practices
378
+
379
+ 1. **Validation**: Validate in constructor, fail fast
380
+ 2. **Immutability**: Keep entities immutable
381
+ 3. **Type Safety**: Use TypeScript for compile-time checks
382
+ 4. **Business Logic**: Encapsulate business logic in entities
383
+ 5. **Equality**: Implement proper equality methods
384
+ 6. **Formatting**: Provide formatting methods
385
+
386
+ ## Related
387
+
388
+ - [Config Domain](../../README.md)
389
+ - [Config Entities](./entities/README.md)
390
+ - [Config Value Objects](./value-objects/README.md)