@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.
@@ -0,0 +1,441 @@
1
+ # RevenueCat Domain Value Objects
2
+
3
+ Value objects for RevenueCat integration.
4
+
5
+ ## Overview
6
+
7
+ This directory contains value objects - immutable types that represent concepts in the RevenueCat domain with no identity. Value objects are defined by their attributes rather than an identity.
8
+
9
+ ## Value Objects
10
+
11
+ ### EntitlementId
12
+
13
+ Represents an entitlement identifier.
14
+
15
+ ```typescript
16
+ class EntitlementId {
17
+ private readonly value: string;
18
+
19
+ constructor(value: string) {
20
+ if (!value || value.trim().length === 0) {
21
+ throw new Error('Entitlement ID cannot be empty');
22
+ }
23
+ this.value = value.trim();
24
+ }
25
+
26
+ getValue(): string {
27
+ return this.value;
28
+ }
29
+
30
+ equals(other: EntitlementId): boolean {
31
+ return this.value === other.value;
32
+ }
33
+
34
+ toString(): string {
35
+ return this.value;
36
+ }
37
+ }
38
+
39
+ // Usage
40
+ const premiumId = new EntitlementId('premium');
41
+ ```
42
+
43
+ ### OfferingId
44
+
45
+ Represents an offering identifier.
46
+
47
+ ```typescript
48
+ class OfferingId {
49
+ private readonly value: string;
50
+
51
+ constructor(value: string) {
52
+ const validIds = ['default', 'annual_offer', 'lifetime_offer'];
53
+ if (!validIds.includes(value)) {
54
+ throw new Error(`Invalid offering ID: ${value}`);
55
+ }
56
+ this.value = value;
57
+ }
58
+
59
+ getValue(): string {
60
+ return this.value;
61
+ }
62
+
63
+ isDefault(): boolean {
64
+ return this.value === 'default';
65
+ }
66
+
67
+ equals(other: OfferingId): boolean {
68
+ return this.value === other.value;
69
+ }
70
+
71
+ toString(): string {
72
+ return this.value;
73
+ }
74
+ }
75
+
76
+ // Usage
77
+ const defaultOffering = new OfferingId('default');
78
+ const annualOffering = new OfferingId('annual_offer');
79
+ ```
80
+
81
+ ### PackageIdentifier
82
+
83
+ Represents a package identifier.
84
+
85
+ ```typescript
86
+ class PackageIdentifier {
87
+ private readonly value: string;
88
+
89
+ constructor(value: string) {
90
+ if (!value || value.trim().length === 0) {
91
+ throw new Error('Package identifier cannot be empty');
92
+ }
93
+ this.value = value;
94
+ }
95
+
96
+ getValue(): string {
97
+ return this.value;
98
+ }
99
+
100
+ getType(): PackageType {
101
+ if (this.value.includes('monthly')) return 'monthly';
102
+ if (this.value.includes('annual')) return 'annual';
103
+ if (this.value.includes('lifetime')) return 'lifetime';
104
+ if (this.value.includes('weekly')) return 'weekly';
105
+ return 'single_purchase';
106
+ }
107
+
108
+ isSubscription(): boolean {
109
+ const type = this.getType();
110
+ return type === 'monthly' || type === 'annual' || type === 'weekly';
111
+ }
112
+
113
+ equals(other: PackageIdentifier): boolean {
114
+ return this.value === other.value;
115
+ }
116
+
117
+ toString(): string {
118
+ return this.value;
119
+ }
120
+ }
121
+
122
+ // Usage
123
+ const monthlyPackage = new PackageIdentifier('$rc_monthly');
124
+ console.log(monthlyPackage.getType()); // 'monthly'
125
+ console.log(monthlyPackage.isSubscription()); // true
126
+ ```
127
+
128
+ ### ProductId
129
+
130
+ Represents a product identifier.
131
+
132
+ ```typescript
133
+ class ProductId {
134
+ private readonly value: string;
135
+
136
+ constructor(value: string) {
137
+ if (!value || value.trim().length === 0) {
138
+ throw new Error('Product ID cannot be empty');
139
+ }
140
+ this.value = value;
141
+ }
142
+
143
+ getValue(): string {
144
+ return this.value;
145
+ }
146
+
147
+ equals(other: ProductId): boolean {
148
+ return this.value === other.value;
149
+ }
150
+
151
+ toString(): string {
152
+ return this.value;
153
+ }
154
+ }
155
+
156
+ // Usage
157
+ const productId = new ProductId('com.app.premium.monthly');
158
+ ```
159
+
160
+ ### Money
161
+
162
+ Represents monetary value with currency.
163
+
164
+ ```typescript
165
+ class Money {
166
+ private readonly amount: number;
167
+ private readonly currency: string;
168
+
169
+ constructor(amount: number, currency: string) {
170
+ if (amount < 0) {
171
+ throw new Error('Amount cannot be negative');
172
+ }
173
+ if (currency.length !== 3) {
174
+ throw new Error('Currency must be ISO 4217 code (3 letters)');
175
+ }
176
+ this.amount = amount;
177
+ this.currency = currency.toUpperCase();
178
+ }
179
+
180
+ getAmount(): number {
181
+ return this.amount;
182
+ }
183
+
184
+ getCurrency(): string {
185
+ return this.currency;
186
+ }
187
+
188
+ format(locale: string = 'en-US'): string {
189
+ return new Intl.NumberFormat(locale, {
190
+ style: 'currency',
191
+ currency: this.currency,
192
+ }).format(this.amount);
193
+ }
194
+
195
+ add(other: Money): Money {
196
+ if (this.currency !== other.currency) {
197
+ throw new Error('Cannot add different currencies');
198
+ }
199
+ return new Money(this.amount + other.amount, this.currency);
200
+ }
201
+
202
+ multiply(factor: number): Money {
203
+ return new Money(this.amount * factor, this.currency);
204
+ }
205
+
206
+ equals(other: Money): boolean {
207
+ return this.amount === other.amount && this.currency === other.currency;
208
+ }
209
+
210
+ toString(): string {
211
+ return this.format();
212
+ }
213
+ }
214
+
215
+ // Usage
216
+ const price = new Money(9.99, 'USD');
217
+ console.log(price.format()); // '$9.99'
218
+ console.log(price.format('tr-TR')); // '9,99 $'
219
+
220
+ const annualPrice = price.multiply(12);
221
+ console.log(annualPrice.format()); // '$119.88'
222
+ ```
223
+
224
+ ### SubscriptionPeriod
225
+
226
+ Represents a subscription period.
227
+
228
+ ```typescript
229
+ class SubscriptionPeriod {
230
+ private readonly value: number;
231
+ private readonly unit: 'day' | 'week' | 'month' | 'year';
232
+
233
+ constructor(value: number, unit: 'day' | 'week' | 'month' | 'year') {
234
+ if (value <= 0) {
235
+ throw new Error('Period value must be positive');
236
+ }
237
+ this.value = value;
238
+ this.unit = unit;
239
+ }
240
+
241
+ getValue(): number {
242
+ return this.value;
243
+ }
244
+
245
+ getUnit(): string {
246
+ return this.unit;
247
+ }
248
+
249
+ inDays(): number {
250
+ switch (this.unit) {
251
+ case 'day': return this.value;
252
+ case 'week': return this.value * 7;
253
+ case 'month': return this.value * 30; // Approximate
254
+ case 'year': return this.value * 365;
255
+ }
256
+ }
257
+
258
+ inMonths(): number {
259
+ switch (this.unit) {
260
+ case 'day': return this.value / 30;
261
+ case 'week': return this.value / 4;
262
+ case 'month': return this.value;
263
+ case 'year': return this.value * 12;
264
+ }
265
+ }
266
+
267
+ format(locale: string = 'en-US'): string {
268
+ const formatter = new Intl.RelativeTimeFormat(locale, { numeric: 'always' });
269
+ return formatter.format(this.value, this.unit);
270
+ }
271
+
272
+ equals(other: SubscriptionPeriod): boolean {
273
+ return this.value === other.value && this.unit === other.unit;
274
+ }
275
+
276
+ toString(): string {
277
+ return this.format();
278
+ }
279
+ }
280
+
281
+ // Usage
282
+ const monthly = new SubscriptionPeriod(1, 'month');
283
+ const annual = new SubscriptionPeriod(1, 'year');
284
+
285
+ console.log(monthly.format()); // '1 month'
286
+ console.log(annual.inDays()); // 365
287
+ console.log(annual.inMonths()); // 12
288
+ ```
289
+
290
+ ### PurchaseToken
291
+
292
+ Represents a purchase transaction token.
293
+
294
+ ```typescript
295
+ class PurchaseToken {
296
+ private readonly value: string;
297
+
298
+ constructor(value: string) {
299
+ if (!value || value.trim().length === 0) {
300
+ throw new Error('Purchase token cannot be empty');
301
+ }
302
+ this.value = value;
303
+ }
304
+
305
+ getValue(): string {
306
+ return this.value;
307
+ }
308
+
309
+ equals(other: PurchaseToken): boolean {
310
+ return this.value === other.value;
311
+ }
312
+
313
+ toString(): string {
314
+ return this.value;
315
+ }
316
+
317
+ // Mask sensitive parts
318
+ mask(): string {
319
+ if (this.value.length <= 8) {
320
+ return '***';
321
+ }
322
+ const start = this.value.substring(0, 4);
323
+ const end = this.value.substring(this.value.length - 4);
324
+ return `${start}...${end}`;
325
+ }
326
+ }
327
+
328
+ // Usage
329
+ const token = new PurchaseToken('abc123def456ghi789');
330
+ console.log(token.mask()); // 'abc1...i789'
331
+ ```
332
+
333
+ ## Usage Examples
334
+
335
+ ### Creating Value Objects
336
+
337
+ ```typescript
338
+ import {
339
+ EntitlementId,
340
+ OfferingId,
341
+ PackageIdentifier,
342
+ Money,
343
+ SubscriptionPeriod,
344
+ } from './value-objects';
345
+
346
+ // Create entitlement ID
347
+ const premiumId = new EntitlementId('premium');
348
+
349
+ // Create offering ID
350
+ const defaultOffering = new OfferingId('default');
351
+
352
+ // Create package identifier
353
+ const monthlyPackage = new PackageIdentifier('$rc_monthly');
354
+
355
+ // Create price
356
+ const price = new Money(9.99, 'USD');
357
+
358
+ // Create subscription period
359
+ const period = new SubscriptionPeriod(1, 'month');
360
+ ```
361
+
362
+ ### Comparing Value Objects
363
+
364
+ ```typescript
365
+ const id1 = new EntitlementId('premium');
366
+ const id2 = new EntitlementId('premium');
367
+ const id3 = new EntitlementId('pro');
368
+
369
+ console.log(id1.equals(id2)); // true
370
+ console.log(id1.equals(id3)); // false
371
+
372
+ const price1 = new Money(9.99, 'USD');
373
+ const price2 = new Money(9.99, 'USD');
374
+ const price3 = new Money(19.99, 'USD');
375
+
376
+ console.log(price1.equals(price2)); // true
377
+ console.log(price1.equals(price3)); // false
378
+ ```
379
+
380
+ ### Performing Operations
381
+
382
+ ```typescript
383
+ // Money arithmetic
384
+ const monthlyPrice = new Money(9.99, 'USD');
385
+ const annualPrice = monthlyPrice.multiply(12);
386
+ const withDiscount = annualPrice.multiply(0.8); // 20% off
387
+
388
+ console.log(monthlyPrice.format()); // '$9.99'
389
+ console.log(annualPrice.format()); // '$119.88'
390
+ console.log(withDiscount.format()); // '$95.90'
391
+
392
+ // Period conversions
393
+ const period = new SubscriptionPeriod(1, 'year');
394
+ console.log(period.inDays()); // 365
395
+ console.log(period.inMonths()); // 12
396
+ console.log(period.format('tr-TR')); // '1 yıl'
397
+ ```
398
+
399
+ ### Validation
400
+
401
+ ```typescript
402
+ // Valid values
403
+ const validEntitlement = new EntitlementId('premium');
404
+ const validMoney = new Money(10.00, 'USD');
405
+ const validPeriod = new SubscriptionPeriod(1, 'month');
406
+
407
+ // Invalid values (will throw)
408
+ try {
409
+ new EntitlementId(''); // Error: Entitlement ID cannot be empty
410
+ } catch (error) {
411
+ console.error(error.message);
412
+ }
413
+
414
+ try {
415
+ new Money(-10, 'USD'); // Error: Amount cannot be negative
416
+ } catch (error) {
417
+ console.error(error.message);
418
+ }
419
+
420
+ try {
421
+ new SubscriptionPeriod(0, 'month'); // Error: Period value must be positive
422
+ } catch (error) {
423
+ console.error(error.message);
424
+ }
425
+ ```
426
+
427
+ ## Best Practices
428
+
429
+ 1. **Immutability**: Never modify value objects after creation
430
+ 2. **Validation**: Validate in constructor, fail fast
431
+ 3. **Equality**: Implement proper equality based on values
432
+ 4. **Formatting**: Provide locale-aware formatting
433
+ 5. **Operations**: Return new instances for operations
434
+ 6. **Type Safety**: Use TypeScript for compile-time checks
435
+ 7. **Serialization**: Provide toString/JSON methods if needed
436
+
437
+ ## Related
438
+
439
+ - [RevenueCat Domain](../README.md)
440
+ - [RevenueCat Entities](../entities/README.md)
441
+ - [RevenueCat Types](../types/README.md)
@@ -0,0 +1,50 @@
1
+ # RevenueCat Infrastructure
2
+
3
+ Infrastructure layer for RevenueCat integration.
4
+
5
+ ## Overview
6
+
7
+ This directory contains concrete implementations of RevenueCat interfaces, handling communication with the RevenueCat SDK and external services.
8
+
9
+ ## Structure
10
+
11
+ ```
12
+ infrastructure/
13
+ ├── handlers/ # Event handlers and callbacks
14
+ ├── services/ # Service implementations
15
+ └── utils/ # Utility functions
16
+ ```
17
+
18
+ ## Components
19
+
20
+ ### Handlers
21
+
22
+ Event handlers for RevenueCat lifecycle events.
23
+
24
+ **See**: [Handlers README](./handlers/README.md)
25
+
26
+ ### Services
27
+
28
+ Service implementations for RevenueCat operations.
29
+
30
+ **See**: [Services README](./services/README.md)
31
+
32
+ ### Utils
33
+
34
+ Utility functions for common operations.
35
+
36
+ **See**: [Utils README](./utils/README.md)
37
+
38
+ ## Key Responsibilities
39
+
40
+ 1. **SDK Integration**: Direct integration with RevenueCat SDK
41
+ 2. **Error Handling**: Converting SDK errors to domain errors
42
+ 3. **Data Transformation**: Mapping SDK types to domain types
43
+ 4. **Event Handling**: Managing purchase and customer info events
44
+ 5. **Configuration**: Setting up and configuring RevenueCat
45
+
46
+ ## Related
47
+
48
+ - [RevenueCat Integration](../README.md)
49
+ - [RevenueCat Application](../application/README.md)
50
+ - [RevenueCat Domain](../domain/README.md)
@@ -0,0 +1,218 @@
1
+ # RevenueCat Infrastructure Handlers
2
+
3
+ Event handlers for RevenueCat lifecycle events.
4
+
5
+ ## Overview
6
+
7
+ This directory contains handler implementations for managing RevenueCat events including purchase callbacks, customer info changes, and error handling.
8
+
9
+ ## Handlers
10
+
11
+ ### PurchaseCompletedHandler
12
+
13
+ Handles successful purchase completion.
14
+
15
+ ```typescript
16
+ class PurchaseCompletedHandler {
17
+ async handle(result: PurchaseResult): Promise<void> {
18
+ // 1. Extract transaction info
19
+ const { customerInfo, transaction } = result;
20
+
21
+ // 2. Update local subscription status
22
+ await updateSubscriptionStatus(customerInfo);
23
+
24
+ // 3. Handle credits allocation
25
+ if (isPremiumPurchase(transaction)) {
26
+ await allocatePremiumCredits(customerInfo.originalAppUserId);
27
+ }
28
+
29
+ // 4. Trigger success callbacks
30
+ triggerSuccessCallbacks(result);
31
+
32
+ // 5. Log purchase
33
+ logPurchaseEvent(transaction);
34
+ }
35
+ }
36
+ ```
37
+
38
+ ### PurchaseErrorHandler
39
+
40
+ Handles purchase errors.
41
+
42
+ ```typescript
43
+ class PurchaseErrorHandler {
44
+ async handle(error: PurchasesError): Promise<void> {
45
+ // 1. Categorize error
46
+ const category = categorizeError(error);
47
+
48
+ // 2. Show appropriate message
49
+ showErrorMessage(category);
50
+
51
+ // 3. Log error
52
+ logPurchaseError(error);
53
+
54
+ // 4. Trigger error callbacks
55
+ triggerErrorCallbacks(error);
56
+
57
+ // 5. Offer recovery options
58
+ if (category === 'network') {
59
+ offerRetry();
60
+ }
61
+ }
62
+ }
63
+ ```
64
+
65
+ ### CustomerInfoChangedHandler
66
+
67
+ Handles customer info changes.
68
+
69
+ ```typescript
70
+ class CustomerInfoChangedHandler {
71
+ async handle(customerInfo: CustomerInfo): Promise<void> {
72
+ // 1. Check for subscription changes
73
+ const changes = detectSubscriptionChanges(customerInfo);
74
+
75
+ // 2. Update local state
76
+ await updateLocalState(customerInfo);
77
+
78
+ // 3. Handle status changes
79
+ if (changes.subscriptionChanged) {
80
+ await handleSubscriptionChange(changes);
81
+ }
82
+
83
+ // 4. Trigger UI refresh
84
+ triggerUIRefresh();
85
+ }
86
+ }
87
+ ```
88
+
89
+ ### EntitlementsChangedHandler
90
+
91
+ Handles entitlement changes.
92
+
93
+ ```typescript
94
+ class EntitlementsChangedHandler {
95
+ async handle(entitlements: EntitlementInfos): Promise<void> {
96
+ // 1. Check premium status
97
+ const premium = entitlements.premium;
98
+
99
+ // 2. Update access controls
100
+ if (premium?.isActive) {
101
+ grantPremiumAccess();
102
+ } else {
103
+ revokePremiumAccess();
104
+ }
105
+
106
+ // 3. Handle new entitlements
107
+ for (const [key, entitlement] of Object.entries(entitlements)) {
108
+ if (entitlement.isActive) {
109
+ await handleNewEntitlement(key, entitlement);
110
+ }
111
+ }
112
+ }
113
+ }
114
+ ```
115
+
116
+ ## Usage
117
+
118
+ ### Setting Up Handlers
119
+
120
+ ```typescript
121
+ import { Purchases } from '@revenuecat/purchases-capacitor';
122
+ import {
123
+ PurchaseCompletedHandler,
124
+ PurchaseErrorHandler,
125
+ CustomerInfoChangedHandler,
126
+ } from './handlers';
127
+
128
+ // Set up customer info listener
129
+ Purchases.addCustomerInfoUpdateListener((customerInfo) => {
130
+ const handler = new CustomerInfoChangedHandler();
131
+ handler.handle(customerInfo);
132
+ });
133
+
134
+ // Use in purchase flow
135
+ async function purchasePackage(pkg: Package) {
136
+ const completedHandler = new PurchaseCompletedHandler();
137
+ const errorHandler = new PurchaseErrorHandler();
138
+
139
+ try {
140
+ const result = await Purchases.purchasePackage({ aPackage: pkg });
141
+ await completedHandler.handle(result);
142
+ } catch (error) {
143
+ await errorHandler.handle(error);
144
+ }
145
+ }
146
+ ```
147
+
148
+ ### Custom Handlers
149
+
150
+ ```typescript
151
+ class CustomPurchaseHandler {
152
+ constructor(
153
+ private onSuccess: (result: PurchaseResult) => void,
154
+ private onError: (error: PurchasesError) => void
155
+ ) {}
156
+
157
+ async handle(result: PurchaseResult | PurchasesError): Promise<void> {
158
+ if (isPurchasesError(result)) {
159
+ await this.onError(result);
160
+ } else {
161
+ await this.onSuccess(result);
162
+ }
163
+ }
164
+ }
165
+
166
+ // Usage
167
+ const handler = new CustomPurchaseHandler(
168
+ (result) => {
169
+ Alert.alert('Success', 'Purchase completed!');
170
+ navigation.navigate('Home');
171
+ },
172
+ (error) => {
173
+ Alert.alert('Error', error.message);
174
+ }
175
+ );
176
+ ```
177
+
178
+ ## Error Categorization
179
+
180
+ ```typescript
181
+ function categorizeError(error: PurchasesError): ErrorCategory {
182
+ switch (error.code) {
183
+ case 'PURCHASE_CANCELLED':
184
+ return 'cancelled';
185
+ case 'NETWORK_ERROR':
186
+ return 'network';
187
+ case 'INVALID_CREDENTIALS_ERROR':
188
+ return 'config';
189
+ case 'PRODUCT_NOT_AVAILABLE_FOR_PURCHASE':
190
+ return 'availability';
191
+ default:
192
+ return 'unknown';
193
+ }
194
+ }
195
+
196
+ type ErrorCategory =
197
+ | 'cancelled'
198
+ | 'network'
199
+ | 'config'
200
+ | 'availability'
201
+ | 'unknown';
202
+ ```
203
+
204
+ ## Best Practices
205
+
206
+ 1. **Immutability**: Don't mutate incoming data
207
+ 2. **Error Boundaries**: Handle errors gracefully
208
+ 3. **Logging**: Log all events for debugging
209
+ 4. **Callbacks**: Invoke registered callbacks appropriately
210
+ 5. **Async Handling**: Use async/await for asynchronous operations
211
+ 6. **Validation**: Validate incoming data before processing
212
+ 7. **Recovery**: Provide recovery options when possible
213
+
214
+ ## Related
215
+
216
+ - [RevenueCat Infrastructure](../README.md)
217
+ - [RevenueCat Services](../services/README.md)
218
+ - [RevenueCat Errors](../../domain/errors/README.md)