@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,385 @@
|
|
|
1
|
+
# Infrastructure Repositories
|
|
2
|
+
|
|
3
|
+
Repository implementations for data persistence.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
Repositories handle data access and persistence, abstracting away the details of storage mechanisms.
|
|
8
|
+
|
|
9
|
+
## Available Repositories
|
|
10
|
+
|
|
11
|
+
### CreditsRepository
|
|
12
|
+
|
|
13
|
+
Manages credits data in Firestore.
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import {
|
|
17
|
+
configureCreditsRepository,
|
|
18
|
+
getCreditsRepository,
|
|
19
|
+
type CreditsRepository,
|
|
20
|
+
} from '@umituz/react-native-subscription/infrastructure';
|
|
21
|
+
|
|
22
|
+
// Configure
|
|
23
|
+
configureCreditsRepository({
|
|
24
|
+
firebase: { firestore: db },
|
|
25
|
+
config: creditsConfig,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Get instance
|
|
29
|
+
const repository = getCreditsRepository();
|
|
30
|
+
|
|
31
|
+
// Use
|
|
32
|
+
const credits = await repository.getCredits('user-123');
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### TransactionRepository
|
|
36
|
+
|
|
37
|
+
Manages transaction history.
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import {
|
|
41
|
+
createTransactionRepository,
|
|
42
|
+
type TransactionRepository,
|
|
43
|
+
} from '@umituz/react-native-subscription/wallet';
|
|
44
|
+
|
|
45
|
+
const repository = createTransactionRepository({
|
|
46
|
+
firebase: { firestore: db },
|
|
47
|
+
userId: 'user-123',
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const transactions = await repository.getTransactions({ limit: 20 });
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Custom Repositories
|
|
54
|
+
|
|
55
|
+
### Creating a Custom Repository
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
import type { ISubscriptionRepository } from '@umituz/react-native-subscription/application';
|
|
59
|
+
|
|
60
|
+
class HttpSubscriptionRepository implements ISubscriptionRepository {
|
|
61
|
+
private baseUrl: string;
|
|
62
|
+
|
|
63
|
+
constructor(baseUrl: string) {
|
|
64
|
+
this.baseUrl = baseUrl;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async getSubscriptionStatus(userId: string): Promise<SubscriptionStatus | null> {
|
|
68
|
+
const response = await fetch(`${this.baseUrl}/subscriptions/${userId}`);
|
|
69
|
+
|
|
70
|
+
if (!response.ok) {
|
|
71
|
+
throw new SubscriptionRepositoryError('Failed to fetch subscription');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return await response.json();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async saveSubscriptionStatus(
|
|
78
|
+
userId: string,
|
|
79
|
+
status: SubscriptionStatus
|
|
80
|
+
): Promise<void> {
|
|
81
|
+
const response = await fetch(`${this.baseUrl}/subscriptions/${userId}`, {
|
|
82
|
+
method: 'PUT',
|
|
83
|
+
headers: { 'Content-Type': 'application/json' },
|
|
84
|
+
body: JSON.stringify(status),
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
if (!response.ok) {
|
|
88
|
+
throw new SubscriptionRepositoryError('Failed to save subscription');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async deleteSubscriptionStatus(userId: string): Promise<void> {
|
|
93
|
+
const response = await fetch(`${this.baseUrl}/subscriptions/${userId}`, {
|
|
94
|
+
method: 'DELETE',
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
if (!response.ok) {
|
|
98
|
+
throw new SubscriptionRepositoryError('Failed to delete subscription');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
isSubscriptionValid(status: SubscriptionStatus): boolean {
|
|
103
|
+
return status.isActive && !isExpired(status.expirationDate);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### In-Memory Repository (for testing)
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
class InMemorySubscriptionRepository implements ISubscriptionRepository {
|
|
112
|
+
private storage = new Map<string, SubscriptionStatus>();
|
|
113
|
+
|
|
114
|
+
async getSubscriptionStatus(userId: string): Promise<SubscriptionStatus | null> {
|
|
115
|
+
return this.storage.get(userId) || null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async saveSubscriptionStatus(
|
|
119
|
+
userId: string,
|
|
120
|
+
status: SubscriptionStatus
|
|
121
|
+
): Promise<void> {
|
|
122
|
+
this.storage.set(userId, status);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async deleteSubscriptionStatus(userId: string): Promise<void> {
|
|
126
|
+
this.storage.delete(userId);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
isSubscriptionValid(status: SubscriptionStatus): boolean {
|
|
130
|
+
return status.isActive && !isExpired(status.expirationDate);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Test helpers
|
|
134
|
+
__reset() {
|
|
135
|
+
this.storage.clear();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Redis Repository
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
import Redis from 'ioredis';
|
|
144
|
+
|
|
145
|
+
class RedisCreditsRepository {
|
|
146
|
+
private redis: Redis;
|
|
147
|
+
private prefix = 'credits:';
|
|
148
|
+
|
|
149
|
+
constructor(redis: Redis) {
|
|
150
|
+
this.redis = redis;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async getCredits(userId: string): Promise<UserCredits> {
|
|
154
|
+
const key = `${this.prefix}${userId}`;
|
|
155
|
+
const data = await this.redis.get(key);
|
|
156
|
+
|
|
157
|
+
if (!data) {
|
|
158
|
+
return {
|
|
159
|
+
balance: 0,
|
|
160
|
+
lastUpdated: new Date().toISOString(),
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return JSON.parse(data);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async setCredits(userId: string, credits: UserCredits): Promise<void> {
|
|
168
|
+
const key = `${this.prefix}${userId}`;
|
|
169
|
+
await this.redis.set(key, JSON.stringify(credits));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async addCredits(
|
|
173
|
+
userId: string,
|
|
174
|
+
amount: number
|
|
175
|
+
): Promise<UserCredits> {
|
|
176
|
+
const current = await this.getCredits(userId);
|
|
177
|
+
const updated = {
|
|
178
|
+
...current,
|
|
179
|
+
balance: current.balance + amount,
|
|
180
|
+
lastUpdated: new Date().toISOString(),
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
await this.setCredits(userId, updated);
|
|
184
|
+
return updated;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Repository Patterns
|
|
190
|
+
|
|
191
|
+
### 1. Active Record Pattern
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
class Subscription {
|
|
195
|
+
constructor(private data: SubscriptionData) {}
|
|
196
|
+
|
|
197
|
+
async save(): Promise<void> {
|
|
198
|
+
await db.collection('subscriptions').doc(this.data.id).set(this.data);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async delete(): Promise<void> {
|
|
202
|
+
await db.collection('subscriptions').doc(this.data.id).delete();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
static async find(id: string): Promise<Subscription | null> {
|
|
206
|
+
const doc = await db.collection('subscriptions').doc(id).get();
|
|
207
|
+
if (!doc.exists) return null;
|
|
208
|
+
return new Subscription(doc.data());
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### 2. Data Mapper Pattern
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
class SubscriptionMapper {
|
|
217
|
+
toEntity(doc: FirebaseFirestore.DocumentSnapshot): Subscription {
|
|
218
|
+
const data = doc.data();
|
|
219
|
+
return Subscription.create({
|
|
220
|
+
id: doc.id,
|
|
221
|
+
...data,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
toData(entity: Subscription): Record<string, any> {
|
|
226
|
+
return {
|
|
227
|
+
type: entity.type,
|
|
228
|
+
isActive: entity.isActive,
|
|
229
|
+
isPremium: entity.isPremium,
|
|
230
|
+
expirationDate: entity.expirationDate,
|
|
231
|
+
willRenew: entity.willRenew,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### 3. Repository with Caching
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
class CachedSubscriptionRepository {
|
|
241
|
+
private cache = new Map<string, { data: SubscriptionStatus; expiry: number }>();
|
|
242
|
+
private ttl = 5 * 60 * 1000; // 5 minutes
|
|
243
|
+
|
|
244
|
+
constructor(private repository: ISubscriptionRepository) {}
|
|
245
|
+
|
|
246
|
+
async getSubscriptionStatus(userId: string): Promise<SubscriptionStatus | null> {
|
|
247
|
+
// Check cache
|
|
248
|
+
const cached = this.cache.get(userId);
|
|
249
|
+
if (cached && cached.expiry > Date.now()) {
|
|
250
|
+
return cached.data;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Fetch from source
|
|
254
|
+
const status = await this.repository.getSubscriptionStatus(userId);
|
|
255
|
+
|
|
256
|
+
// Update cache
|
|
257
|
+
if (status) {
|
|
258
|
+
this.cache.set(userId, {
|
|
259
|
+
data: status,
|
|
260
|
+
expiry: Date.now() + this.ttl,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return status;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
invalidate(userId: string): void {
|
|
268
|
+
this.cache.delete(userId);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
invalidateAll(): void {
|
|
272
|
+
this.cache.clear();
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## Transaction Support
|
|
278
|
+
|
|
279
|
+
### Firestore Transactions
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
class TransactionalCreditsRepository {
|
|
283
|
+
async transferCredits(
|
|
284
|
+
fromUserId: string,
|
|
285
|
+
toUserId: string,
|
|
286
|
+
amount: number
|
|
287
|
+
): Promise<void> {
|
|
288
|
+
await db.runTransaction(async (transaction) => {
|
|
289
|
+
const fromRef = db.collection('credits').doc(fromUserId);
|
|
290
|
+
const toRef = db.collection('credits').doc(toUserId);
|
|
291
|
+
|
|
292
|
+
const fromDoc = await transaction.get(fromRef);
|
|
293
|
+
const toDoc = await transaction.get(toRef);
|
|
294
|
+
|
|
295
|
+
const fromBalance = fromDoc.data()?.balance || 0;
|
|
296
|
+
const toBalance = toDoc.data()?.balance || 0;
|
|
297
|
+
|
|
298
|
+
if (fromBalance < amount) {
|
|
299
|
+
throw new InsufficientCreditsError('Insufficient credits');
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
transaction.update(fromRef, { balance: fromBalance - amount });
|
|
303
|
+
transaction.update(toRef, { balance: toBalance + amount });
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
## Real-Time Updates
|
|
310
|
+
|
|
311
|
+
### Firestore Realtime Listener
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
class RealtimeSubscriptionRepository {
|
|
315
|
+
subscribeToStatus(
|
|
316
|
+
userId: string,
|
|
317
|
+
callback: (status: SubscriptionStatus) => void
|
|
318
|
+
): () => void {
|
|
319
|
+
const unsubscribe = db
|
|
320
|
+
.collection('subscriptions')
|
|
321
|
+
.doc(userId)
|
|
322
|
+
.onSnapshot((doc) => {
|
|
323
|
+
if (doc.exists) {
|
|
324
|
+
callback(doc.data() as SubscriptionStatus);
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
return unsubscribe;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
## Testing Repositories
|
|
334
|
+
|
|
335
|
+
### Mock Repository Factory
|
|
336
|
+
|
|
337
|
+
```typescript
|
|
338
|
+
function createMockRepository(options?: {
|
|
339
|
+
initialData?: Map<string, SubscriptionStatus>;
|
|
340
|
+
latency?: number;
|
|
341
|
+
}): ISubscriptionRepository {
|
|
342
|
+
const data = options?.initialData || new Map();
|
|
343
|
+
const latency = options?.latency || 0;
|
|
344
|
+
|
|
345
|
+
return {
|
|
346
|
+
async getSubscriptionStatus(userId: string) {
|
|
347
|
+
if (latency > 0) {
|
|
348
|
+
await sleep(latency);
|
|
349
|
+
}
|
|
350
|
+
return data.get(userId) || null;
|
|
351
|
+
},
|
|
352
|
+
|
|
353
|
+
async saveSubscriptionStatus(userId: string, status: SubscriptionStatus) {
|
|
354
|
+
if (latency > 0) {
|
|
355
|
+
await sleep(latency);
|
|
356
|
+
}
|
|
357
|
+
data.set(userId, status);
|
|
358
|
+
},
|
|
359
|
+
|
|
360
|
+
async deleteSubscriptionStatus(userId: string) {
|
|
361
|
+
data.delete(userId);
|
|
362
|
+
},
|
|
363
|
+
|
|
364
|
+
isSubscriptionValid(status: SubscriptionStatus) {
|
|
365
|
+
return status.isActive && !isExpired(status.expirationDate);
|
|
366
|
+
},
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
## Best Practices
|
|
372
|
+
|
|
373
|
+
1. **Interface Segregation** - Keep interfaces focused
|
|
374
|
+
2. **Error Handling** - Transform storage errors to domain errors
|
|
375
|
+
3. **Logging** - Log repository operations
|
|
376
|
+
4. **Validation** - Validate data before saving
|
|
377
|
+
5. **Performance** - Use caching and batching
|
|
378
|
+
6. **Testing** - Mock repositories for unit tests
|
|
379
|
+
7. **Transactions** - Use transactions for multi-document updates
|
|
380
|
+
|
|
381
|
+
## Related
|
|
382
|
+
|
|
383
|
+
- [Infrastructure Layer](../../README.md)
|
|
384
|
+
- [Infrastructure Services](../services/README.md)
|
|
385
|
+
- [Application Ports](../../application/ports/README.md)
|