@umituz/react-native-subscription 2.14.97 → 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.
Files changed (90) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +461 -0
  3. package/package.json +1 -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/README.md +240 -0
  12. package/src/domains/config/README.md +390 -0
  13. package/src/domains/config/domain/README.md +390 -0
  14. package/src/domains/config/domain/entities/README.md +350 -0
  15. package/src/domains/paywall/README.md +371 -0
  16. package/src/domains/paywall/components/PaywallHeader.tsx +8 -11
  17. package/src/domains/paywall/components/README.md +185 -0
  18. package/src/domains/paywall/entities/README.md +199 -0
  19. package/src/domains/paywall/hooks/README.md +129 -0
  20. package/src/domains/wallet/README.md +292 -0
  21. package/src/domains/wallet/domain/README.md +108 -0
  22. package/src/domains/wallet/domain/entities/README.md +122 -0
  23. package/src/domains/wallet/domain/errors/README.md +157 -0
  24. package/src/domains/wallet/infrastructure/README.md +96 -0
  25. package/src/domains/wallet/presentation/components/BalanceCard.tsx +6 -12
  26. package/src/domains/wallet/presentation/components/README.md +231 -0
  27. package/src/domains/wallet/presentation/hooks/README.md +255 -0
  28. package/src/infrastructure/README.md +514 -0
  29. package/src/infrastructure/mappers/README.md +34 -0
  30. package/src/infrastructure/models/README.md +26 -0
  31. package/src/infrastructure/repositories/README.md +385 -0
  32. package/src/infrastructure/services/README.md +374 -0
  33. package/src/presentation/README.md +410 -0
  34. package/src/presentation/components/README.md +183 -0
  35. package/src/presentation/components/details/CreditRow.md +337 -0
  36. package/src/presentation/components/details/DetailRow.md +283 -0
  37. package/src/presentation/components/details/PremiumDetailsCard.md +266 -0
  38. package/src/presentation/components/details/PremiumStatusBadge.md +266 -0
  39. package/src/presentation/components/details/README.md +449 -0
  40. package/src/presentation/components/feedback/PaywallFeedbackModal.md +314 -0
  41. package/src/presentation/components/feedback/README.md +447 -0
  42. package/src/presentation/components/paywall/PaywallModal.md +444 -0
  43. package/src/presentation/components/paywall/README.md +190 -0
  44. package/src/presentation/components/sections/README.md +468 -0
  45. package/src/presentation/components/sections/SubscriptionSection.md +246 -0
  46. package/src/presentation/hooks/README.md +743 -0
  47. package/src/presentation/hooks/useAuthAwarePurchase.md +359 -0
  48. package/src/presentation/hooks/useAuthGate.md +403 -0
  49. package/src/presentation/hooks/useAuthSubscriptionSync.md +398 -0
  50. package/src/presentation/hooks/useCreditChecker.md +407 -0
  51. package/src/presentation/hooks/useCredits.md +342 -0
  52. package/src/presentation/hooks/useCreditsGate.md +346 -0
  53. package/src/presentation/hooks/useDeductCredit.md +176 -0
  54. package/src/presentation/hooks/useDevTestCallbacks.md +422 -0
  55. package/src/presentation/hooks/useFeatureGate.md +157 -0
  56. package/src/presentation/hooks/useInitializeCredits.md +458 -0
  57. package/src/presentation/hooks/usePaywall.md +334 -0
  58. package/src/presentation/hooks/usePaywallOperations.md +486 -0
  59. package/src/presentation/hooks/usePaywallVisibility.md +344 -0
  60. package/src/presentation/hooks/usePremium.md +230 -0
  61. package/src/presentation/hooks/usePremiumGate.md +423 -0
  62. package/src/presentation/hooks/usePremiumWithCredits.md +429 -0
  63. package/src/presentation/hooks/useSubscription.md +450 -0
  64. package/src/presentation/hooks/useSubscriptionDetails.md +438 -0
  65. package/src/presentation/hooks/useSubscriptionGate.md +168 -0
  66. package/src/presentation/hooks/useSubscriptionSettingsConfig.md +374 -0
  67. package/src/presentation/hooks/useSubscriptionStatus.md +424 -0
  68. package/src/presentation/hooks/useUserTier.md +356 -0
  69. package/src/presentation/hooks/useUserTierWithRepository.md +452 -0
  70. package/src/presentation/screens/README.md +194 -0
  71. package/src/presentation/types/README.md +38 -0
  72. package/src/presentation/utils/README.md +52 -0
  73. package/src/revenuecat/README.md +523 -0
  74. package/src/revenuecat/application/README.md +158 -0
  75. package/src/revenuecat/application/ports/README.md +169 -0
  76. package/src/revenuecat/domain/README.md +147 -0
  77. package/src/revenuecat/domain/constants/README.md +183 -0
  78. package/src/revenuecat/domain/entities/README.md +382 -0
  79. package/src/revenuecat/domain/errors/README.md +197 -0
  80. package/src/revenuecat/domain/types/README.md +373 -0
  81. package/src/revenuecat/domain/value-objects/README.md +441 -0
  82. package/src/revenuecat/infrastructure/README.md +50 -0
  83. package/src/revenuecat/infrastructure/config/README.md +40 -0
  84. package/src/revenuecat/infrastructure/handlers/README.md +218 -0
  85. package/src/revenuecat/infrastructure/managers/README.md +49 -0
  86. package/src/revenuecat/infrastructure/services/README.md +325 -0
  87. package/src/revenuecat/infrastructure/utils/README.md +382 -0
  88. package/src/revenuecat/presentation/README.md +184 -0
  89. package/src/revenuecat/presentation/hooks/README.md +56 -0
  90. package/src/utils/README.md +529 -0
@@ -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)
@@ -0,0 +1,49 @@
1
+ # RevenueCat Infrastructure Managers
2
+
3
+ Manager classes for coordinating RevenueCat operations.
4
+
5
+ ## Overview
6
+
7
+ This directory contains high-level manager classes that coordinate between different RevenueCat services and handle complex operations.
8
+
9
+ ## Contents
10
+
11
+ - **SubscriptionManager.ts** - Manages RevenueCat configuration and entitlement access
12
+
13
+ ## SubscriptionManager
14
+
15
+ Manages RevenueCat SDK configuration and provides access to entitlement IDs.
16
+
17
+ ### Key Methods
18
+
19
+ ```typescript
20
+ class SubscriptionManager {
21
+ static configure(config: RevenueCatConfig): void;
22
+ static getEntitlementId(): string | null;
23
+ static getOfferings(): Promise<Offerings>;
24
+ }
25
+ ```
26
+
27
+ ### Usage
28
+
29
+ ```typescript
30
+ import { SubscriptionManager } from './managers/SubscriptionManager';
31
+
32
+ // Configure at app startup
33
+ SubscriptionManager.configure({
34
+ apiKey: 'your_api_key',
35
+ entitlementId: 'premium',
36
+ });
37
+
38
+ // Get entitlement ID
39
+ const entitlementId = SubscriptionManager.getEntitlementId();
40
+
41
+ // Get offerings
42
+ const offerings = await SubscriptionManager.getOfferings();
43
+ ```
44
+
45
+ ## Related
46
+
47
+ - [Services](../services/README.md)
48
+ - [Config](../config/README.md)
49
+ - [Domain](../../domain/README.md)
@@ -0,0 +1,325 @@
1
+ # RevenueCat Infrastructure Services
2
+
3
+ Service implementations for RevenueCat operations.
4
+
5
+ ## Overview
6
+
7
+ This directory contains concrete implementations of RevenueCat interfaces, providing the actual integration with the RevenueCat SDK.
8
+
9
+ ## Services
10
+
11
+ ### RevenueCatServiceImpl
12
+
13
+ Main service implementation for RevenueCat operations.
14
+
15
+ ```typescript
16
+ import { Purchases } from '@revenuecat/purchases-capacitor';
17
+ import type { IRevenueCatService } from '../../application/ports/IRevenueCatService';
18
+
19
+ class RevenueCatServiceImpl implements IRevenueCatService {
20
+ // Configuration
21
+ async configure(params: {
22
+ apiKey: string;
23
+ userId?: string;
24
+ observerMode?: boolean;
25
+ }): Promise<void> {
26
+ await Purchases.configure({
27
+ apiKey: params.apiKey,
28
+ appUserID: params.userId,
29
+ observerMode: params.observerMode ?? false,
30
+ });
31
+ }
32
+
33
+ // Purchasing
34
+ async purchasePackage(pkg: Package): Promise<PurchaseResult> {
35
+ return await Purchases.purchasePackage({ aPackage: pkg });
36
+ }
37
+
38
+ async purchaseProduct(productId: string): Promise<PurchaseResult> {
39
+ return await Purchases.purchaseProduct({ productIdentifier: productId });
40
+ }
41
+
42
+ async restorePurchases(): Promise<RestoreResult> {
43
+ return await Purchases.restorePurchases();
44
+ }
45
+
46
+ // Customer Information
47
+ async getCustomerInfo(): Promise<CustomerInfo> {
48
+ return await Purchases.getCustomerInfo();
49
+ }
50
+
51
+ async getCustomerInfoUserId(): Promise<string | null> {
52
+ const info = await this.getCustomerInfo();
53
+ return info.originalAppUserId;
54
+ }
55
+
56
+ // Offerings
57
+ async getOfferings(): Promise<Offerings> {
58
+ return await Purchases.getOfferings();
59
+ }
60
+
61
+ async getCurrentOffering(): Promise<Offering | null> {
62
+ const offerings = await this.getOfferings();
63
+ return offerings.current;
64
+ }
65
+
66
+ // Entitlements
67
+ async checkEntitlement(entitlementId: string): Promise<boolean> {
68
+ const info = await this.getCustomerInfo();
69
+ return info.entitlements[entitlementId]?.isActive ?? false;
70
+ }
71
+
72
+ async checkEntitlementInfo(
73
+ entitlementId: string
74
+ ): Promise<EntitlementInfo | null> {
75
+ const info = await this.getCustomerInfo();
76
+ return info.entitlements[entitlementId] ?? null;
77
+ }
78
+
79
+ // Subscriber Attributes
80
+ async setAttributes(attributes: SubscriberAttributes): Promise<void> {
81
+ await Purchases.setAttributes(attributes);
82
+ }
83
+
84
+ async setEmail(email: string): Promise<void> {
85
+ await Purchases.setEmail(email);
86
+ }
87
+
88
+ async setPhoneNumber(phoneNumber: string): Promise<void> {
89
+ await Purchases.setPhoneNumber(phoneNumber);
90
+ }
91
+
92
+ // Log Out
93
+ async logOut(): Promise<void> {
94
+ await Purchases.logOut();
95
+ }
96
+ }
97
+ ```
98
+
99
+ ### RevenueCatServiceProvider
100
+
101
+ Provides the RevenueCat service instance.
102
+
103
+ ```typescript
104
+ class RevenueCatServiceProvider {
105
+ private static instance: IRevenueCatService | null = null;
106
+
107
+ static getInstance(): IRevenueCatService {
108
+ if (!this.instance) {
109
+ this.instance = new RevenueCatServiceImpl();
110
+ }
111
+ return this.instance;
112
+ }
113
+
114
+ static configure(config: {
115
+ apiKey: string;
116
+ userId?: string;
117
+ observerMode?: boolean;
118
+ }): IRevenueCatService {
119
+ const service = this.getInstance();
120
+ service.configure(config);
121
+ return service;
122
+ }
123
+ }
124
+ ```
125
+
126
+ ## Usage
127
+
128
+ ### Initializing the Service
129
+
130
+ ```typescript
131
+ import { RevenueCatServiceProvider } from './services/RevenueCatServiceProvider';
132
+
133
+ // Configure RevenueCat
134
+ const service = RevenueCatServiceProvider.configure({
135
+ apiKey: 'your_api_key',
136
+ userId: user?.uid,
137
+ observerMode: false,
138
+ });
139
+ ```
140
+
141
+ ### Making Purchases
142
+
143
+ ```typescript
144
+ import { RevenueCatServiceProvider } from './services/RevenueCatServiceProvider';
145
+
146
+ async function purchasePremium(userId: string) {
147
+ const service = RevenueCatServiceProvider.getInstance();
148
+
149
+ // Get offerings
150
+ const offerings = await service.getOfferings();
151
+ const monthlyPackage = offerings.current?.monthly;
152
+
153
+ if (!monthlyPackage) {
154
+ throw new Error('No package available');
155
+ }
156
+
157
+ // Purchase
158
+ const result = await service.purchasePackage(monthlyPackage);
159
+
160
+ if (result.error) {
161
+ throw new Error(result.error.message);
162
+ }
163
+
164
+ // Check entitlement
165
+ const hasPremium = await service.checkEntitlement('premium');
166
+ if (hasPremium) {
167
+ console.log('Premium activated!');
168
+ }
169
+
170
+ return result;
171
+ }
172
+ ```
173
+
174
+ ### Restoring Purchases
175
+
176
+ ```typescript
177
+ async function restorePurchases() {
178
+ const service = RevenueCatServiceProvider.getInstance();
179
+
180
+ try {
181
+ const result = await service.restorePurchases();
182
+
183
+ if (result.error) {
184
+ throw new Error(result.error.message);
185
+ }
186
+
187
+ // Update local state
188
+ await updateSubscriptionStatus(result.customerInfo);
189
+
190
+ return result.customerInfo;
191
+ } catch (error) {
192
+ console.error('Restore failed:', error);
193
+ throw error;
194
+ }
195
+ }
196
+ ```
197
+
198
+ ### Checking Entitlements
199
+
200
+ ```typescript
201
+ async function checkPremiumAccess(): Promise<boolean> {
202
+ const service = RevenueCatServiceProvider.getInstance();
203
+
204
+ try {
205
+ const isActive = await service.checkEntitlement('premium');
206
+
207
+ if (isActive) {
208
+ const entitlement = await service.checkEntitlementInfo('premium');
209
+ console.log('Premium details:', entitlement);
210
+ }
211
+
212
+ return isActive;
213
+ } catch (error) {
214
+ console.error('Failed to check entitlement:', error);
215
+ return false;
216
+ }
217
+ }
218
+ ```
219
+
220
+ ### Setting Subscriber Attributes
221
+
222
+ ```typescript
223
+ async function updateUserAttributes(user: User) {
224
+ const service = RevenueCatServiceProvider.getInstance();
225
+
226
+ // Set email
227
+ if (user.email) {
228
+ await service.setEmail(user.email);
229
+ }
230
+
231
+ // Set phone number
232
+ if (user.phoneNumber) {
233
+ await service.setPhoneNumber(user.phoneNumber);
234
+ }
235
+
236
+ // Set custom attributes
237
+ await service.setAttributes({
238
+ $displayName: user.displayName,
239
+ account_created_at: user.createdAt.toISOString(),
240
+ subscription_tier: user.tier,
241
+ });
242
+ }
243
+ ```
244
+
245
+ ## Error Handling
246
+
247
+ ### Wrapping Service Calls
248
+
249
+ ```typescript
250
+ class RevenueCatServiceWrapper {
251
+ private service: IRevenueCatService;
252
+
253
+ constructor() {
254
+ this.service = RevenueCatServiceProvider.getInstance();
255
+ }
256
+
257
+ async safePurchase(
258
+ pkg: Package
259
+ ): Promise<PurchaseResult> {
260
+ try {
261
+ return await this.service.purchasePackage(pkg);
262
+ } catch (error) {
263
+ // Convert to domain error
264
+ if (isPurchasesError(error)) {
265
+ return {
266
+ customerInfo: {} as CustomerInfo,
267
+ error,
268
+ };
269
+ }
270
+ throw error;
271
+ }
272
+ }
273
+
274
+ async safeGetOfferings(): Promise<Offerings | null> {
275
+ try {
276
+ return await this.getOfferings();
277
+ } catch (error) {
278
+ console.error('Failed to get offerings:', error);
279
+ return null;
280
+ }
281
+ }
282
+ }
283
+ ```
284
+
285
+ ## Testing
286
+
287
+ ### Mock Implementation
288
+
289
+ ```typescript
290
+ class MockRevenueCatService implements IRevenueCatService {
291
+ async configure(): Promise<void> {
292
+ console.log('Mock: Configure called');
293
+ }
294
+
295
+ async purchasePackage(pkg: Package): Promise<PurchaseResult> {
296
+ console.log('Mock: Purchase package', pkg.identifier);
297
+ return {
298
+ customerInfo: mockCustomerInfo,
299
+ transaction: mockTransaction,
300
+ };
301
+ }
302
+
303
+ // ... other mock implementations
304
+ }
305
+
306
+ // Use in tests
307
+ const mockService = new MockRevenueCatService();
308
+ ```
309
+
310
+ ## Best Practices
311
+
312
+ 1. **Singleton**: Use singleton pattern for service instance
313
+ 2. **Error Handling**: Wrap all service calls in try-catch
314
+ 3. **Type Safety**: Use TypeScript types for all operations
315
+ 4. **Logging**: Log all RevenueCat operations
316
+ 5. **Caching**: Cache customer info and offerings appropriately
317
+ 6. **Validation**: Validate parameters before calling SDK
318
+ 7. **Configuration**: Configure service only once
319
+ 8. **Testing**: Use mock implementations for testing
320
+
321
+ ## Related
322
+
323
+ - [RevenueCat Infrastructure](../README.md)
324
+ - [RevenueCat Handlers](../handlers/README.md)
325
+ - [RevenueCat Application Ports](../../application/ports/README.md)