@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.
- package/LICENSE +21 -0
- package/README.md +462 -0
- package/package.json +3 -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/revenuecat/presentation/hooks/usePurchasePackage.ts +1 -1
- package/src/utils/README.md +529 -0
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
# Infrastructure Services
|
|
2
|
+
|
|
3
|
+
Service implementations in the infrastructure layer.
|
|
4
|
+
|
|
5
|
+
## Services
|
|
6
|
+
|
|
7
|
+
### SubscriptionService
|
|
8
|
+
|
|
9
|
+
Main service for subscription management.
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
import { SubscriptionService } from '@umituz/react-native-subscription/infrastructure';
|
|
13
|
+
|
|
14
|
+
const service = new SubscriptionService({
|
|
15
|
+
repository: myRepository,
|
|
16
|
+
onStatusChanged: (userId, status) => {
|
|
17
|
+
console.log(`Status changed for ${userId}`);
|
|
18
|
+
},
|
|
19
|
+
onError: (error) => {
|
|
20
|
+
console.error('Error:', error);
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### SubscriptionInitializer
|
|
26
|
+
|
|
27
|
+
Initializes the subscription system on app startup.
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import { initializeSubscription } from '@umituz/react-native-subscription/infrastructure';
|
|
31
|
+
|
|
32
|
+
await initializeSubscription({
|
|
33
|
+
revenueCatApiKey: 'your_key',
|
|
34
|
+
revenueCatEntitlementId: 'premium',
|
|
35
|
+
creditPackages: [...],
|
|
36
|
+
});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Features
|
|
40
|
+
|
|
41
|
+
### 1. Automatic Status Sync
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
const service = new SubscriptionService({
|
|
45
|
+
repository: myRepository,
|
|
46
|
+
onStatusChanged: (userId, newStatus) => {
|
|
47
|
+
// React to status changes
|
|
48
|
+
analytics.track('subscription_changed', {
|
|
49
|
+
userId,
|
|
50
|
+
status: newStatus.type,
|
|
51
|
+
});
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 2. Error Handling
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
const service = new SubscriptionService({
|
|
60
|
+
repository: myRepository,
|
|
61
|
+
onError: (error) => {
|
|
62
|
+
if (error instanceof SubscriptionValidationError) {
|
|
63
|
+
// Log validation errors
|
|
64
|
+
logger.warn('Validation error:', error.message);
|
|
65
|
+
} else {
|
|
66
|
+
// Log critical errors
|
|
67
|
+
logger.error('Service error:', error);
|
|
68
|
+
crashlytics().recordError(error);
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### 3. Caching
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
const service = new SubscriptionService({
|
|
78
|
+
repository: myRepository,
|
|
79
|
+
cache: {
|
|
80
|
+
enabled: true,
|
|
81
|
+
ttl: 5 * 60 * 1000, // 5 minutes
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Custom Services
|
|
87
|
+
|
|
88
|
+
### Creating a Custom Service
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
import type { ISubscriptionService } from '@umituz/react-native-subscription/application';
|
|
92
|
+
|
|
93
|
+
export class CustomSubscriptionService implements ISubscriptionService {
|
|
94
|
+
constructor(
|
|
95
|
+
private repository: ISubscriptionRepository,
|
|
96
|
+
private apiClient: ApiClient
|
|
97
|
+
) {}
|
|
98
|
+
|
|
99
|
+
async getStatus(userId: string): Promise<SubscriptionStatus | null> {
|
|
100
|
+
// Try cache first
|
|
101
|
+
const cached = await this.cache.get(userId);
|
|
102
|
+
if (cached) return cached;
|
|
103
|
+
|
|
104
|
+
// Fetch from repository
|
|
105
|
+
const status = await this.repository.getSubscriptionStatus(userId);
|
|
106
|
+
|
|
107
|
+
// Update cache
|
|
108
|
+
if (status) {
|
|
109
|
+
await this.cache.set(userId, status, { ttl: 300000 });
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return status;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async activateSubscription(
|
|
116
|
+
userId: string,
|
|
117
|
+
productId: string,
|
|
118
|
+
expiresAt: string | null
|
|
119
|
+
): Promise<SubscriptionStatus> {
|
|
120
|
+
// Business logic
|
|
121
|
+
const current = await this.getStatus(userId);
|
|
122
|
+
if (current?.isActive) {
|
|
123
|
+
throw new SubscriptionOperationError(
|
|
124
|
+
'User already has an active subscription'
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Activate
|
|
129
|
+
const status = await this.repository.saveSubscriptionStatus(userId, {
|
|
130
|
+
type: 'premium',
|
|
131
|
+
isActive: true,
|
|
132
|
+
isPremium: true,
|
|
133
|
+
expirationDate: expiresAt,
|
|
134
|
+
willRenew: expiresAt !== null,
|
|
135
|
+
productId,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
return status;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async deactivateSubscription(userId: string): Promise<SubscriptionStatus> {
|
|
142
|
+
const status = await this.getStatus(userId);
|
|
143
|
+
if (!status?.isActive) {
|
|
144
|
+
throw new SubscriptionOperationError('Subscription is not active');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const deactivated = {
|
|
148
|
+
...status,
|
|
149
|
+
isActive: false,
|
|
150
|
+
willRenew: false,
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
await this.repository.saveSubscriptionStatus(userId, deactivated);
|
|
154
|
+
return deactivated;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async isPremium(userId: string): Promise<boolean> {
|
|
158
|
+
const status = await this.getStatus(userId);
|
|
159
|
+
return status?.isPremium ?? false;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Advanced Features
|
|
165
|
+
|
|
166
|
+
### Retry Logic
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
class SubscriptionServiceWithRetry {
|
|
170
|
+
async getStatus(userId: string): Promise<SubscriptionStatus | null> {
|
|
171
|
+
return this.retryOperation(
|
|
172
|
+
() => this.repository.getSubscriptionStatus(userId),
|
|
173
|
+
{
|
|
174
|
+
maxRetries: 3,
|
|
175
|
+
delay: 1000,
|
|
176
|
+
backoff: 'exponential',
|
|
177
|
+
}
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private async retryOperation<T>(
|
|
182
|
+
operation: () => Promise<T>,
|
|
183
|
+
options: RetryOptions
|
|
184
|
+
): Promise<T> {
|
|
185
|
+
let lastError;
|
|
186
|
+
|
|
187
|
+
for (let attempt = 0; attempt < options.maxRetries; attempt++) {
|
|
188
|
+
try {
|
|
189
|
+
return await operation();
|
|
190
|
+
} catch (error) {
|
|
191
|
+
lastError = error;
|
|
192
|
+
|
|
193
|
+
if (attempt < options.maxRetries - 1) {
|
|
194
|
+
const delay = options.backoff === 'exponential'
|
|
195
|
+
? Math.pow(2, attempt) * options.delay
|
|
196
|
+
: options.delay;
|
|
197
|
+
|
|
198
|
+
await sleep(delay);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
throw lastError;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Circuit Breaker
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
class CircuitBreaker {
|
|
212
|
+
private failures = 0;
|
|
213
|
+
private lastFailureTime: number | null = null;
|
|
214
|
+
private state: 'closed' | 'open' | 'half-open' = 'closed';
|
|
215
|
+
|
|
216
|
+
async execute<T>(operation: () => Promise<T>): Promise<T> {
|
|
217
|
+
if (this.state === 'open') {
|
|
218
|
+
if (this.shouldAttemptReset()) {
|
|
219
|
+
this.state = 'half-open';
|
|
220
|
+
} else {
|
|
221
|
+
throw new Error('Circuit breaker is open');
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
const result = await operation();
|
|
227
|
+
this.onSuccess();
|
|
228
|
+
return result;
|
|
229
|
+
} catch (error) {
|
|
230
|
+
this.onFailure();
|
|
231
|
+
throw error;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private onSuccess() {
|
|
236
|
+
this.failures = 0;
|
|
237
|
+
this.state = 'closed';
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
private onFailure() {
|
|
241
|
+
this.failures++;
|
|
242
|
+
this.lastFailureTime = Date.now();
|
|
243
|
+
|
|
244
|
+
if (this.failures >= 5) {
|
|
245
|
+
this.state = 'open';
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
private shouldAttemptReset(): boolean {
|
|
250
|
+
return (
|
|
251
|
+
this.lastFailureTime !== null &&
|
|
252
|
+
Date.now() - this.lastFailureTime > 60000 // 1 minute
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Observability
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
class ObservableSubscriptionService {
|
|
262
|
+
private eventEmitter = new EventEmitter();
|
|
263
|
+
|
|
264
|
+
async getStatus(userId: string): Promise<SubscriptionStatus | null> {
|
|
265
|
+
const startTime = Date.now();
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
const status = await this.repository.getSubscriptionStatus(userId);
|
|
269
|
+
|
|
270
|
+
this.eventEmitter.emit('operation_success', {
|
|
271
|
+
operation: 'getStatus',
|
|
272
|
+
userId,
|
|
273
|
+
duration: Date.now() - startTime,
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
return status;
|
|
277
|
+
} catch (error) {
|
|
278
|
+
this.eventEmitter.emit('operation_error', {
|
|
279
|
+
operation: 'getStatus',
|
|
280
|
+
userId,
|
|
281
|
+
error: error.message,
|
|
282
|
+
duration: Date.now() - startTime,
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
throw error;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
on(event: string, listener: (data: any) => void) {
|
|
290
|
+
this.eventEmitter.on(event, listener);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
off(event: string, listener: (data: any) => void) {
|
|
294
|
+
this.eventEmitter.off(event, listener);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
## Testing
|
|
300
|
+
|
|
301
|
+
### Unit Tests
|
|
302
|
+
|
|
303
|
+
```typescript
|
|
304
|
+
describe('SubscriptionService', () => {
|
|
305
|
+
let service: SubscriptionService;
|
|
306
|
+
let mockRepository: jest.Mocked<ISubscriptionRepository>;
|
|
307
|
+
|
|
308
|
+
beforeEach(() => {
|
|
309
|
+
mockRepository = {
|
|
310
|
+
getSubscriptionStatus: jest.fn(),
|
|
311
|
+
saveSubscriptionStatus: jest.fn(),
|
|
312
|
+
deleteSubscriptionStatus: jest.fn(),
|
|
313
|
+
isSubscriptionValid: jest.fn(),
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
service = new SubscriptionService({
|
|
317
|
+
repository: mockRepository,
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it('should get subscription status', async () => {
|
|
322
|
+
const expectedStatus = { type: 'premium', isActive: true };
|
|
323
|
+
mockRepository.getSubscriptionStatus.mockResolvedValue(expectedStatus);
|
|
324
|
+
|
|
325
|
+
const status = await service.getStatus('user-123');
|
|
326
|
+
|
|
327
|
+
expect(status).toEqual(expectedStatus);
|
|
328
|
+
expect(mockRepository.getSubscriptionStatus).toHaveBeenCalledWith('user-123');
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### Integration Tests
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
describe('SubscriptionService Integration', () => {
|
|
337
|
+
let service: SubscriptionService;
|
|
338
|
+
let repository: FirebaseSubscriptionRepository;
|
|
339
|
+
|
|
340
|
+
beforeAll(async () => {
|
|
341
|
+
// Use test Firebase instance
|
|
342
|
+
const testDb = await initializeTestFirebase();
|
|
343
|
+
repository = new FirebaseSubscriptionRepository(testDb);
|
|
344
|
+
service = new SubscriptionService({ repository });
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it('should activate subscription', async () => {
|
|
348
|
+
const status = await service.activateSubscription(
|
|
349
|
+
'user-123',
|
|
350
|
+
'premium_monthly',
|
|
351
|
+
'2025-12-31'
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
expect(status.type).toBe('premium');
|
|
355
|
+
expect(status.isActive).toBe(true);
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
## Best Practices
|
|
361
|
+
|
|
362
|
+
1. **Dependency Injection** - Accept dependencies via constructor
|
|
363
|
+
2. **Error Handling** - Handle and transform errors appropriately
|
|
364
|
+
3. **Logging** - Log important operations
|
|
365
|
+
4. **Caching** - Cache frequently accessed data
|
|
366
|
+
5. **Validation** - Validate inputs
|
|
367
|
+
6. **Type Safety** - Use TypeScript types
|
|
368
|
+
7. **Testing** - Write comprehensive tests
|
|
369
|
+
|
|
370
|
+
## Related
|
|
371
|
+
|
|
372
|
+
- [Application Layer](../../application/README.md)
|
|
373
|
+
- [Application Ports](../../application/ports/README.md)
|
|
374
|
+
- [Infrastructure Layer](../../README.md)
|