@umituz/react-native-subscription 2.27.65 → 2.27.67

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 (121) hide show
  1. package/package.json +3 -1
  2. package/src/domains/credits/application/CreditLimitCalculator.ts +17 -0
  3. package/src/domains/credits/application/CreditsInitializer.ts +85 -0
  4. package/src/domains/credits/application/DeductCreditsCommand.ts +52 -0
  5. package/src/domains/credits/application/PurchaseMetadataGenerator.ts +59 -0
  6. package/src/domains/credits/application/credit-strategies/CreditAllocationContext.ts +35 -0
  7. package/src/domains/credits/application/credit-strategies/ICreditStrategy.ts +18 -0
  8. package/src/domains/credits/application/credit-strategies/StandardPurchaseCreditStrategy.ts +16 -0
  9. package/src/domains/credits/application/credit-strategies/SyncCreditStrategy.ts +15 -0
  10. package/src/domains/credits/application/credit-strategies/TrialCreditStrategy.ts +18 -0
  11. package/src/{infrastructure/mappers → domains/credits/core}/CreditsMapper.ts +4 -4
  12. package/src/domains/credits/infrastructure/CreditsRepository.ts +102 -0
  13. package/src/{presentation/hooks → domains/credits/presentation}/useCredits.ts +21 -4
  14. package/src/domains/subscription/application/SubscriptionAuthListener.ts +26 -0
  15. package/src/domains/subscription/application/SubscriptionInitializer.ts +77 -0
  16. package/src/{infrastructure/services → domains/subscription/application}/SubscriptionInitializerTypes.ts +21 -1
  17. package/src/domains/subscription/application/SubscriptionSyncService.ts +71 -0
  18. package/src/domains/subscription/application/SubscriptionSyncUtils.ts +16 -0
  19. package/src/{revenuecat/domain/value-objects → domains/subscription/core}/RevenueCatConfig.ts +1 -1
  20. package/src/{domain/types → domains/subscription/core}/RevenueCatData.ts +1 -1
  21. package/src/{domain/entities → domains/subscription/core}/SubscriptionStatus.ts +13 -21
  22. package/src/domains/subscription/core/SubscriptionStatusHandlers.ts +51 -0
  23. package/src/domains/subscription/infrastructure/handlers/PackageHandler.ts +67 -0
  24. package/src/domains/subscription/infrastructure/handlers/PurchaseStatusResolver.ts +27 -0
  25. package/src/domains/subscription/infrastructure/managers/SubscriptionInternalState.ts +12 -0
  26. package/src/domains/subscription/infrastructure/managers/SubscriptionManager.ts +110 -0
  27. package/src/{presentation/hooks → domains/subscription/presentation}/usePremium.ts +7 -4
  28. package/src/domains/trial/application/TrialEligibilityService.ts +25 -0
  29. package/src/domains/trial/application/TrialService.ts +68 -0
  30. package/src/{infrastructure/services → domains/trial/core}/TrialTypes.ts +1 -1
  31. package/src/domains/trial/infrastructure/DeviceTrialRepository.ts +30 -0
  32. package/src/presentation/components/details/PremiumStatusBadge.tsx +2 -2
  33. package/src/presentation/hooks/index.ts +11 -11
  34. package/src/shared/infrastructure/SubscriptionEventBus.ts +51 -0
  35. package/src/utils/packageTypeDetector.ts +13 -18
  36. package/src/application/README.md +0 -50
  37. package/src/domain/entities/README.md +0 -50
  38. package/src/domain/entities/SubscriptionStatus.test.ts +0 -105
  39. package/src/domain/errors/README.md +0 -53
  40. package/src/domain/value-objects/README.md +0 -50
  41. package/src/infrastructure/README.md +0 -55
  42. package/src/infrastructure/mappers/README.md +0 -21
  43. package/src/infrastructure/models/README.md +0 -26
  44. package/src/infrastructure/repositories/CreditsRepository.ts +0 -132
  45. package/src/infrastructure/repositories/README.md +0 -99
  46. package/src/infrastructure/services/CreditsInitializer.ts +0 -170
  47. package/src/infrastructure/services/README.md +0 -99
  48. package/src/infrastructure/services/SubscriptionInitializer.ts +0 -176
  49. package/src/infrastructure/services/SubscriptionService.ts +0 -133
  50. package/src/infrastructure/services/TrialService.ts +0 -197
  51. package/src/infrastructure/services/app-service-helpers.ts +0 -111
  52. package/src/revenuecat/README.md +0 -104
  53. package/src/revenuecat/application/README.md +0 -43
  54. package/src/revenuecat/application/ports/IRevenueCatService.ts +0 -76
  55. package/src/revenuecat/application/ports/README.md +0 -41
  56. package/src/revenuecat/domain/README.md +0 -48
  57. package/src/revenuecat/domain/constants/README.md +0 -41
  58. package/src/revenuecat/domain/entities/README.md +0 -42
  59. package/src/revenuecat/domain/errors/README.md +0 -53
  60. package/src/revenuecat/domain/types/README.md +0 -41
  61. package/src/revenuecat/domain/value-objects/README.md +0 -41
  62. package/src/revenuecat/index.ts +0 -13
  63. package/src/revenuecat/infrastructure/handlers/PackageHandler.ts +0 -161
  64. package/src/revenuecat/infrastructure/managers/SubscriptionManager.ts +0 -165
  65. package/src/revenuecat/presentation/README.md +0 -42
  66. /package/src/{domain/entities → domains/credits/core}/Credits.ts +0 -0
  67. /package/src/{infrastructure/models → domains/credits/core}/UserCreditsDocument.ts +0 -0
  68. /package/src/{infrastructure/repositories → domains/credits/infrastructure}/CreditsRepositoryProvider.ts +0 -0
  69. /package/src/{presentation/hooks → domains/credits/presentation}/useDeductCredit.ts +0 -0
  70. /package/src/{revenuecat/domain/constants → domains/subscription/core}/RevenueCatConstants.ts +0 -0
  71. /package/src/{revenuecat/domain/errors → domains/subscription/core}/RevenueCatError.ts +0 -0
  72. /package/src/{revenuecat/domain/types → domains/subscription/core}/RevenueCatTypes.ts +0 -0
  73. /package/src/{domain/entities → domains/subscription/core}/SubscriptionConstants.ts +0 -0
  74. /package/src/{revenuecat → domains/subscription}/infrastructure/README.md +0 -0
  75. /package/src/{revenuecat → domains/subscription}/infrastructure/config/README.md +0 -0
  76. /package/src/{revenuecat → domains/subscription}/infrastructure/handlers/README.md +0 -0
  77. /package/src/{revenuecat/presentation → domains/subscription/infrastructure}/hooks/README.md +0 -0
  78. /package/src/{revenuecat/presentation → domains/subscription/infrastructure}/hooks/subscriptionQueryKeys.ts +0 -0
  79. /package/src/{revenuecat/presentation → domains/subscription/infrastructure}/hooks/useCustomerInfo.ts +0 -0
  80. /package/src/{revenuecat/presentation → domains/subscription/infrastructure}/hooks/useInitializeSubscription.ts +0 -0
  81. /package/src/{revenuecat/presentation → domains/subscription/infrastructure}/hooks/usePaywallFlow.ts +0 -0
  82. /package/src/{revenuecat/presentation → domains/subscription/infrastructure}/hooks/usePurchasePackage.ts +0 -0
  83. /package/src/{revenuecat/presentation → domains/subscription/infrastructure}/hooks/useRestorePurchase.ts +0 -0
  84. /package/src/{revenuecat/presentation → domains/subscription/infrastructure}/hooks/useRevenueCat.ts +0 -0
  85. /package/src/{revenuecat/presentation → domains/subscription/infrastructure}/hooks/useRevenueCatTrialEligibility.ts +0 -0
  86. /package/src/{revenuecat/presentation → domains/subscription/infrastructure}/hooks/useSubscriptionPackages.ts +0 -0
  87. /package/src/{revenuecat/presentation → domains/subscription/infrastructure}/hooks/useSubscriptionQueries.ts +0 -0
  88. /package/src/{revenuecat → domains/subscription}/infrastructure/managers/README.md +0 -0
  89. /package/src/{revenuecat → domains/subscription}/infrastructure/services/CustomerInfoListenerManager.ts +0 -0
  90. /package/src/{revenuecat → domains/subscription}/infrastructure/services/OfferingsFetcher.ts +0 -0
  91. /package/src/{revenuecat → domains/subscription}/infrastructure/services/PurchaseHandler.ts +0 -0
  92. /package/src/{revenuecat → domains/subscription}/infrastructure/services/README.md +0 -0
  93. /package/src/{revenuecat → domains/subscription}/infrastructure/services/RestoreHandler.ts +0 -0
  94. /package/src/{revenuecat → domains/subscription}/infrastructure/services/RevenueCatInitializer.ts +0 -0
  95. /package/src/{revenuecat → domains/subscription}/infrastructure/services/RevenueCatService.ts +0 -0
  96. /package/src/{revenuecat → domains/subscription}/infrastructure/services/ServiceStateManager.ts +0 -0
  97. /package/src/{revenuecat → domains/subscription}/infrastructure/utils/ApiKeyResolver.ts +0 -0
  98. /package/src/{revenuecat → domains/subscription}/infrastructure/utils/InitializationCache.ts +0 -0
  99. /package/src/{revenuecat → domains/subscription}/infrastructure/utils/PremiumStatusSyncer.ts +0 -0
  100. /package/src/{revenuecat → domains/subscription}/infrastructure/utils/README.md +0 -0
  101. /package/src/{revenuecat → domains/subscription}/infrastructure/utils/RenewalDetector.ts +0 -0
  102. /package/src/{revenuecat → domains/subscription}/infrastructure/utils/UserIdProvider.ts +0 -0
  103. /package/src/{presentation/hooks → domains/subscription/presentation}/useAuthAwarePurchase.ts +0 -0
  104. /package/src/{presentation/hooks → domains/subscription/presentation}/useAuthSubscriptionSync.ts +0 -0
  105. /package/src/{presentation/hooks → domains/subscription/presentation}/useFeatureGate.ts +0 -0
  106. /package/src/{presentation/hooks → domains/subscription/presentation}/usePaywallVisibility.ts +0 -0
  107. /package/src/{presentation/hooks → domains/subscription/presentation}/usePremiumGate.ts +0 -0
  108. /package/src/{presentation/hooks → domains/subscription/presentation}/useSavedPurchaseAutoExecution.ts +0 -0
  109. /package/src/{presentation/hooks → domains/subscription/presentation}/useSubscriptionSettingsConfig.ts +0 -0
  110. /package/src/{presentation/hooks → domains/subscription/presentation}/useSubscriptionSettingsConfig.utils.ts +0 -0
  111. /package/src/{presentation/hooks → domains/subscription/presentation}/useSubscriptionStatus.ts +0 -0
  112. /package/src/{infrastructure/services → shared/application}/ActivationHandler.ts +0 -0
  113. /package/src/{infrastructure/services → shared/application}/FeedbackService.ts +0 -0
  114. /package/src/{application → shared/application}/ports/ISubscriptionRepository.ts +0 -0
  115. /package/src/{application → shared/application}/ports/ISubscriptionService.ts +0 -0
  116. /package/src/{application → shared/application}/ports/README.md +0 -0
  117. /package/src/{domain/errors → shared/utils}/InsufficientCreditsError.ts +0 -0
  118. /package/src/{infrastructure → shared}/utils/Logger.ts +0 -0
  119. /package/src/{domain/value-objects → shared/utils}/Result.ts +0 -0
  120. /package/src/{domain/value-objects → shared/utils}/SubscriptionConfig.ts +0 -0
  121. /package/src/{domain/errors → shared/utils}/SubscriptionError.ts +0 -0
@@ -1,99 +0,0 @@
1
- # Infrastructure Repositories
2
-
3
- Repository implementations for data persistence.
4
-
5
- ## Location
6
-
7
- - **Base Path**: `/Users/umituz/Desktop/github/umituz/apps/artificial_intelligence/npm-packages/react-native-subscription/src/infrastructure/repositories/`
8
- - **Repositories**: `src/infrastructure/repositories/`
9
-
10
- ## Strategy
11
-
12
- ### Repository Pattern
13
-
14
- Data access abstraction layer.
15
-
16
- - **CreditsRepository**: Credits data management
17
- - **TransactionRepository**: Transaction history
18
- - **SubscriptionRepository**: Subscription status management
19
- - **Custom Repositories**: Extensible repository architecture
20
-
21
- ### Data Access Patterns
22
-
23
- Structured data access implementations.
24
-
25
- - **Active Record**: Entity-based data management
26
- - **Data Mapper**: Separation of entities and data
27
- - **Repository with Caching**: Performance optimization
28
- - **Transaction Support**: Atomic operations
29
-
30
- ### Storage Implementations
31
-
32
- Multiple storage backend support.
33
-
34
- - **Firestore**: Cloud Firestore implementation
35
- - **In-Memory**: Testing and development
36
- - **HTTP**: REST API integration
37
- - **Redis**: Redis cache implementation
38
-
39
- ### Advanced Features
40
-
41
- Enterprise-level repository capabilities.
42
-
43
- - **Real-Time Updates**: Firestore real-time listeners
44
- - **Transaction Support**: Multi-document transactions
45
- - **Caching**: Performance optimization
46
- - **Mocking**: Testing support
47
-
48
- ## Restrictions
49
-
50
- ### REQUIRED
51
-
52
- - **Interface Implementation**: Implement required interfaces
53
- - **Error Handling**: Transform storage errors to domain errors
54
- - **Validation**: Validate data before saving
55
- - **Logging**: Log repository operations
56
-
57
- ### PROHIBITED
58
-
59
- - **Business Logic**: Keep business logic in services
60
- - **Direct Exposure**: Never expose storage-specific types
61
- - **Missing Validation**: Never save invalid data
62
- - **Swallowed Errors**: Always handle or propagate errors
63
-
64
- ### CRITICAL
65
-
66
- - **Error Transformation**: Transform storage errors appropriately
67
- - **Data Integrity**: Maintain data integrity
68
- - **Performance**: Optimize with caching and batching
69
- - **Testing**: Support mock implementations
70
-
71
- ## AI Agent Guidelines
72
-
73
- ### When Modifying Repositories
74
-
75
- 1. **Interface Compliance**: Maintain interface contracts
76
- 2. **Error Handling**: Transform errors appropriately
77
- 3. **Validation**: Validate all data
78
- 4. **Logging**: Log operations for debugging
79
-
80
- ### When Creating Custom Repositories
81
-
82
- 1. **Interface Implementation**: Implement required interfaces
83
- 2. **Error Transformation**: Transform storage errors
84
- 3. **Validation**: Add data validation
85
- 4. **Testing**: Create mock implementations
86
-
87
- ### When Fixing Repository Bugs
88
-
89
- 1. **Data Access**: Check data access logic
90
- 2. **Error Handling**: Verify error transformation
91
- 3. **Validation**: Check validation rules
92
- 4. **Edge Cases**: Test with null/undefined values
93
-
94
- ## Related Documentation
95
-
96
- - [Infrastructure Services](/Users/umituz/Desktop/github/umituz/apps/artificial_intelligence/npm-packages/react-native-subscription/src/infrastructure/services/README.md)
97
- - [Infrastructure Layer](/Users/umituz/Desktop/github/umituz/apps/artificial_intelligence/npm-packages/react-native-subscription/src/infrastructure/README.md)
98
- - [Application Ports](/Users/umituz/Desktop/github/umituz/apps/artificial_intelligence/npm-packages/react-native-subscription/src/application/ports/README.md)
99
- - [Domain Layer](/Users/umituz/Desktop/github/umituz/apps/artificial_intelligence/npm-packages/react-native-subscription/src/domain/README.md)
@@ -1,170 +0,0 @@
1
- /**
2
- * Credits Initializer
3
- * Handles subscription credit initialization for premium users
4
- */
5
-
6
- import { Platform } from "react-native";
7
- import Constants from "expo-constants";
8
- import {
9
- runTransaction,
10
- serverTimestamp,
11
- Timestamp,
12
- type Firestore,
13
- type FieldValue,
14
- type Transaction,
15
- type DocumentReference,
16
- } from "firebase/firestore";
17
- import type { CreditsConfig } from "../../domain/entities/Credits";
18
- import type {
19
- UserCreditsDocumentRead,
20
- PurchaseSource,
21
- PurchaseType,
22
- PurchaseMetadata,
23
- } from "../models/UserCreditsDocument";
24
- import { SUBSCRIPTION_STATUS, resolveSubscriptionStatus, type PeriodType } from "../../domain/entities/SubscriptionStatus";
25
- import { TRIAL_CONFIG } from "./TrialService";
26
- import { detectPackageType } from "../../utils/packageTypeDetector";
27
- import { getCreditAllocation } from "../../utils/creditMapper";
28
-
29
- interface InitializationResult {
30
- credits: number;
31
- alreadyProcessed?: boolean;
32
- }
33
-
34
- export interface InitializeCreditsMetadata {
35
- productId?: string;
36
- source?: PurchaseSource;
37
- type?: PurchaseType;
38
- expirationDate?: string | null;
39
- willRenew?: boolean;
40
- originalTransactionId?: string;
41
- isPremium?: boolean;
42
- periodType?: PeriodType;
43
- }
44
-
45
- export async function initializeCreditsTransaction(
46
- db: Firestore,
47
- creditsRef: DocumentReference,
48
- config: CreditsConfig,
49
- purchaseId?: string,
50
- metadata?: InitializeCreditsMetadata
51
- ): Promise<InitializationResult> {
52
- return runTransaction(db, async (transaction: Transaction) => {
53
- const creditsDoc = await transaction.get(creditsRef);
54
- const now = serverTimestamp();
55
- const existingData = creditsDoc.exists() ? creditsDoc.data() as UserCreditsDocumentRead : null;
56
-
57
- let purchasedAt: FieldValue = now;
58
- let processedPurchases: string[] = existingData?.processedPurchases || [];
59
-
60
- if (existingData && purchaseId && processedPurchases.includes(purchaseId)) {
61
- return { credits: existingData.credits, alreadyProcessed: true };
62
- }
63
-
64
- if (existingData?.purchasedAt) {
65
- purchasedAt = existingData.purchasedAt as unknown as FieldValue;
66
- }
67
-
68
- if (purchaseId) {
69
- processedPurchases = [...processedPurchases, purchaseId].slice(-10);
70
- }
71
-
72
- const productId = metadata?.productId;
73
- const packageType = productId ? detectPackageType(productId) : undefined;
74
- const allocation = packageType && packageType !== "unknown"
75
- ? getCreditAllocation(packageType, config.packageAllocations)
76
- : null;
77
- const creditLimit = allocation ?? config.creditLimit;
78
-
79
- const platform = Platform.OS as "ios" | "android";
80
- const appVersion = Constants.expoConfig?.version;
81
-
82
- let purchaseType: PurchaseType = metadata?.type ?? "initial";
83
- if (existingData?.packageType && packageType !== "unknown") {
84
- const oldLimit = existingData.creditLimit || 0;
85
- if (creditLimit > oldLimit) purchaseType = "upgrade";
86
- else if (creditLimit < oldLimit) purchaseType = "downgrade";
87
- else if (purchaseId?.startsWith("renewal_")) purchaseType = "renewal";
88
- }
89
-
90
- const purchaseMetadata: PurchaseMetadata | undefined =
91
- productId && metadata?.source && packageType && packageType !== "unknown" ? {
92
- productId,
93
- packageType,
94
- creditLimit,
95
- source: metadata.source,
96
- type: purchaseType,
97
- platform,
98
- appVersion,
99
- timestamp: Timestamp.fromDate(new Date()) as unknown as PurchaseMetadata["timestamp"],
100
- } : undefined;
101
-
102
- const purchaseHistory = purchaseMetadata
103
- ? [...(existingData?.purchaseHistory || []), purchaseMetadata].slice(-10)
104
- : existingData?.purchaseHistory;
105
-
106
- const isPremium = metadata?.isPremium ?? true;
107
- const willRenew = metadata?.willRenew;
108
- const periodType = metadata?.periodType;
109
-
110
- const expirationDateStr = metadata?.expirationDate;
111
- const isExpired = expirationDateStr ? new Date(expirationDateStr).getTime() < Date.now() : false;
112
- const status = resolveSubscriptionStatus({ isPremium, willRenew, isExpired, periodType });
113
-
114
- // Determine if this is a status sync (not a new purchase or renewal)
115
- // Status sync should preserve existing credits, only update metadata
116
- const isStatusSync = purchaseId?.startsWith("status_sync_") ?? false;
117
- const isNewPurchaseOrRenewal = purchaseId?.startsWith("purchase_") || purchaseId?.startsWith("renewal_");
118
-
119
- let newCredits = creditLimit;
120
- if (status === SUBSCRIPTION_STATUS.TRIAL) {
121
- newCredits = TRIAL_CONFIG.CREDITS;
122
- } else if (status === SUBSCRIPTION_STATUS.TRIAL_CANCELED) {
123
- newCredits = 0;
124
- } else if (isStatusSync && existingData?.credits !== undefined && existingData.isPremium) {
125
- // Status sync for existing premium user: preserve current credits
126
- newCredits = existingData.credits;
127
- }
128
-
129
- const creditsData: Record<string, unknown> = {
130
- isPremium,
131
- status,
132
- credits: newCredits,
133
- creditLimit,
134
- purchasedAt,
135
- lastUpdatedAt: now,
136
- lastPurchaseAt: isNewPurchaseOrRenewal ? now : (existingData?.lastPurchaseAt ?? now),
137
- processedPurchases,
138
- };
139
-
140
- if (metadata?.expirationDate) creditsData.expirationDate = Timestamp.fromDate(new Date(metadata.expirationDate));
141
- if (metadata?.willRenew !== undefined) creditsData.willRenew = metadata.willRenew;
142
- if (metadata?.originalTransactionId) creditsData.originalTransactionId = metadata.originalTransactionId;
143
- if (packageType && packageType !== "unknown") creditsData.packageType = packageType;
144
- if (productId) {
145
- creditsData.productId = productId;
146
- creditsData.platform = platform;
147
- creditsData.appVersion = appVersion;
148
- }
149
-
150
- const isTrialing = status === SUBSCRIPTION_STATUS.TRIAL || status === SUBSCRIPTION_STATUS.TRIAL_CANCELED;
151
- if (periodType) creditsData.periodType = periodType;
152
- if (isTrialing) {
153
- creditsData.isTrialing = status === SUBSCRIPTION_STATUS.TRIAL;
154
- creditsData.trialCredits = TRIAL_CONFIG.CREDITS;
155
- if (!existingData?.trialStartDate) creditsData.trialStartDate = now;
156
- if (metadata?.expirationDate) creditsData.trialEndDate = Timestamp.fromDate(new Date(metadata.expirationDate));
157
- } else if (existingData?.isTrialing && isPremium) {
158
- creditsData.isTrialing = false;
159
- creditsData.convertedFromTrial = true;
160
- }
161
-
162
- if (metadata?.source) creditsData.purchaseSource = metadata.source;
163
- if (metadata?.type) creditsData.purchaseType = purchaseType;
164
- if (purchaseHistory?.length) creditsData.purchaseHistory = purchaseHistory;
165
-
166
- transaction.set(creditsRef, creditsData, { merge: true });
167
-
168
- return { credits: newCredits };
169
- });
170
- }
@@ -1,99 +0,0 @@
1
- # Infrastructure Services
2
-
3
- Service implementations in the infrastructure layer.
4
-
5
- ## Location
6
-
7
- - **Base Path**: `/Users/umituz/Desktop/github/umituz/apps/artificial_intelligence/npm-packages/react-native-subscription/src/infrastructure/services/`
8
- - **Services**: `src/infrastructure/services/`
9
-
10
- ## Strategy
11
-
12
- ### Service Architecture
13
-
14
- Business logic implementation for subscription operations.
15
-
16
- - **SubscriptionService**: Main subscription management service
17
- - **SubscriptionInitializer**: System initialization
18
- - **Status Sync**: Automatic status synchronization
19
- - **Error Handling**: Comprehensive error management
20
-
21
- ### Service Features
22
-
23
- Core service capabilities.
24
-
25
- - **Automatic Status Sync**: Real-time status updates
26
- - **Error Handling**: Robust error management
27
- - **Caching**: Performance optimization through caching
28
- - **Event Handling**: Status change event callbacks
29
-
30
- ### Advanced Patterns
31
-
32
- Enterprise-grade service patterns.
33
-
34
- - **Retry Logic**: Automatic retry on failures
35
- - **Circuit Breaker**: Fault tolerance patterns
36
- - **Observability**: Event emission and tracking
37
- - **Transaction Support**: Atomic operations
38
-
39
- ### Custom Services
40
-
41
- Extensible service architecture.
42
-
43
- - **Interface Implementation**: Custom service implementations
44
- - **Dependency Injection**: Flexible dependency management
45
- - **Testing Support**: Test-friendly design
46
- - **Configuration**: Service-level configuration
47
-
48
- ## Restrictions
49
-
50
- ### REQUIRED
51
-
52
- - **Dependency Injection**: Use constructor injection
53
- - **Error Handling**: Handle all errors appropriately
54
- - **Logging**: Log important operations
55
- - **Validation**: Validate all inputs
56
-
57
- ### PROHIBITED
58
-
59
- - **Direct Storage Access**: Access storage only through repositories
60
- - **Missing Error Handling**: Never swallow errors
61
- - **Hardcoded Values**: Use configuration
62
- - **Circular Dependencies**: Avoid circular dependencies
63
-
64
- ### CRITICAL
65
-
66
- - **Error Transformation**: Transform storage errors to domain errors
67
- - **Performance**: Optimize with caching
68
- - **Reliability**: Implement retry logic
69
- - **Observability**: Track all operations
70
-
71
- ## AI Agent Guidelines
72
-
73
- ### When Modifying Services
74
-
75
- 1. **Interface Compliance**: Maintain interface contracts
76
- 2. **Error Handling**: Comprehensive error handling
77
- 3. **Logging**: Add logging for debugging
78
- 4. **Testing**: Write comprehensive tests
79
-
80
- ### When Creating Custom Services
81
-
82
- 1. **Interface Implementation**: Implement required interfaces
83
- 2. **Dependency Injection**: Inject dependencies via constructor
84
- 3. **Error Handling**: Transform errors appropriately
85
- 4. **Documentation**: Document behavior and contracts
86
-
87
- ### When Fixing Service Bugs
88
-
89
- 1. **Repository Integration**: Check repository calls
90
- 2. **Error Handling**: Verify error transformation
91
- 3. **Business Logic**: Check business rules
92
- 4. **Edge Cases**: Test boundary conditions
93
-
94
- ## Related Documentation
95
-
96
- - [Infrastructure Repositories](/Users/umituz/Desktop/github/umituz/apps/artificial_intelligence/npm-packages/react-native-subscription/src/infrastructure/repositories/README.md)
97
- - [Application Layer](/Users/umituz/Desktop/github/umituz/apps/artificial_intelligence/npm-packages/react-native-subscription/src/application/README.md)
98
- - [Application Ports](/Users/umituz/Desktop/github/umituz/apps/artificial_intelligence/npm-packages/react-native-subscription/src/application/ports/README.md)
99
- - [Infrastructure Layer](/Users/umituz/Desktop/github/umituz/apps/artificial_intelligence/npm-packages/react-native-subscription/src/infrastructure/README.md)
@@ -1,176 +0,0 @@
1
- /**
2
- * Subscription Initializer
3
- *
4
- * Uses RevenueCat best practices:
5
- * - Non-blocking initialization (fire and forget)
6
- * - Relies on CustomerInfoUpdateListener for state updates
7
- * - No manual timeouts - uses auth state listener with cleanup
8
- */
9
-
10
- import { Platform } from "react-native";
11
- import type { CustomerInfo } from "react-native-purchases";
12
- import { configureCreditsRepository, getCreditsRepository } from "../repositories/CreditsRepositoryProvider";
13
- import { SubscriptionManager } from "../../revenuecat/infrastructure/managers/SubscriptionManager";
14
- import { configureAuthProvider } from "../../presentation/hooks/useAuthAwarePurchase";
15
- import type { RevenueCatData } from "../../domain/types/RevenueCatData";
16
- import { PERIOD_TYPE, type PeriodType, type PurchaseSource } from "../../domain/entities/SubscriptionConstants";
17
- import type { SubscriptionInitConfig, FirebaseAuthLike } from "./SubscriptionInitializerTypes";
18
-
19
- export type { FirebaseAuthLike, CreditPackageConfig, SubscriptionInitConfig } from "./SubscriptionInitializerTypes";
20
-
21
- /**
22
- * Gets the current user ID from Firebase auth.
23
- * Uses listener pattern for reliability instead of timeout.
24
- * Falls back immediately if no auth is available.
25
- */
26
- const getCurrentUserId = (getAuth: () => FirebaseAuthLike | null): string | undefined => {
27
- const auth = getAuth();
28
- if (!auth) return undefined;
29
- return auth.currentUser?.uid;
30
- };
31
-
32
- /**
33
- * Sets up auth state listener that will re-initialize subscription
34
- * when user auth state changes (login/logout).
35
- */
36
- const setupAuthStateListener = (
37
- getAuth: () => FirebaseAuthLike | null,
38
- onUserChange: (userId: string | undefined) => void
39
- ): (() => void) | null => {
40
- const auth = getAuth();
41
- if (!auth) return null;
42
-
43
- return auth.onAuthStateChanged((user) => {
44
- onUserChange(user?.uid);
45
- });
46
- };
47
-
48
- /** Extract RevenueCat data from CustomerInfo (Single Source of Truth) */
49
- const extractRevenueCatData = (customerInfo: CustomerInfo, entitlementId: string): RevenueCatData => {
50
- const entitlement = customerInfo.entitlements.active[entitlementId]
51
- ?? customerInfo.entitlements.all[entitlementId];
52
- return {
53
- expirationDate: entitlement?.expirationDate ?? customerInfo.latestExpirationDate ?? null,
54
- willRenew: entitlement?.willRenew ?? false,
55
- originalTransactionId: entitlement?.originalPurchaseDate ?? undefined,
56
- periodType: entitlement?.periodType as PeriodType | undefined,
57
- };
58
- };
59
-
60
- export const initializeSubscription = async (config: SubscriptionInitConfig): Promise<void> => {
61
- const {
62
- apiKey, apiKeyIos, apiKeyAndroid, entitlementId, credits,
63
- getAnonymousUserId, getFirebaseAuth, showAuthModal,
64
- onCreditsUpdated, creditPackages,
65
- } = config;
66
-
67
- const key = Platform.OS === 'ios' ? (apiKeyIos || apiKey || '') : (apiKeyAndroid || apiKey || '');
68
- if (!key) throw new Error('API key required');
69
-
70
- configureCreditsRepository({ ...credits, creditPackageAmounts: creditPackages?.amounts });
71
-
72
- const onPurchase = async (userId: string, productId: string, customerInfo: CustomerInfo, source?: PurchaseSource) => {
73
- if (__DEV__) console.log('[SubscriptionInitializer] onPurchase:', { userId, productId, source });
74
- try {
75
- const revenueCatData = extractRevenueCatData(customerInfo, entitlementId);
76
- await getCreditsRepository().initializeCredits(userId, `purchase_${productId}_${Date.now()}`, productId, source, revenueCatData);
77
- onCreditsUpdated?.(userId);
78
- } catch (error) {
79
- if (__DEV__) console.error('[SubscriptionInitializer] Credits init failed:', error);
80
- }
81
- };
82
-
83
- const onRenewal = async (userId: string, productId: string, newExpirationDate: string, customerInfo: CustomerInfo) => {
84
- if (__DEV__) console.log('[SubscriptionInitializer] onRenewal:', { userId, productId });
85
- try {
86
- const revenueCatData = extractRevenueCatData(customerInfo, entitlementId);
87
- revenueCatData.expirationDate = newExpirationDate || revenueCatData.expirationDate;
88
- await getCreditsRepository().initializeCredits(userId, `renewal_${productId}_${Date.now()}`, productId, "renewal", revenueCatData);
89
- onCreditsUpdated?.(userId);
90
- } catch (error) {
91
- if (__DEV__) console.error('[SubscriptionInitializer] Renewal credits init failed:', error);
92
- }
93
- };
94
-
95
- const onPremiumStatusChanged = async (
96
- userId: string, isPremium: boolean, productId?: string,
97
- expiresAt?: string, willRenew?: boolean, periodType?: PeriodType
98
- ) => {
99
- if (__DEV__) console.log('[SubscriptionInitializer] onPremiumStatusChanged:', { userId, isPremium, productId, willRenew, periodType });
100
- try {
101
- // If premium became false, check if actually expired or just canceled
102
- if (!isPremium && productId) {
103
- const isActuallyExpired = !expiresAt || new Date(expiresAt) < new Date();
104
- if (isActuallyExpired) {
105
- await getCreditsRepository().syncExpiredStatus(userId);
106
- if (__DEV__) console.log('[SubscriptionInitializer] Subscription expired, synced status');
107
- } else {
108
- if (__DEV__) console.log('[SubscriptionInitializer] Canceled but not expired, preserving until:', expiresAt);
109
- const revenueCatData: RevenueCatData = { expirationDate: expiresAt, willRenew: false, isPremium: true, periodType };
110
- await getCreditsRepository().initializeCredits(userId, `status_sync_canceled_${Date.now()}`, productId, "settings", revenueCatData);
111
- }
112
- onCreditsUpdated?.(userId);
113
- return;
114
- }
115
-
116
- // Premium user - initialize credits with subscription data
117
- const revenueCatData: RevenueCatData = { expirationDate: expiresAt ?? null, willRenew: willRenew ?? false, isPremium, periodType };
118
- await getCreditsRepository().initializeCredits(userId, `status_sync_${Date.now()}`, productId, "settings", revenueCatData);
119
- if (__DEV__) console.log('[SubscriptionInitializer] Premium status synced to Firestore');
120
- onCreditsUpdated?.(userId);
121
- } catch (error) {
122
- if (__DEV__) console.error('[SubscriptionInitializer] Premium status sync failed:', error);
123
- }
124
- };
125
-
126
- SubscriptionManager.configure({
127
- config: {
128
- apiKey: key,
129
- entitlementIdentifier: entitlementId,
130
- consumableProductIdentifiers: [creditPackages?.identifierPattern || 'credit'],
131
- onPurchaseCompleted: onPurchase,
132
- onRenewalDetected: onRenewal,
133
- onPremiumStatusChanged,
134
- onCreditsUpdated,
135
- },
136
- apiKey: key,
137
- getAnonymousUserId,
138
- });
139
-
140
- // Configure auth provider immediately (sync)
141
- configureAuthProvider({
142
- isAuthenticated: () => {
143
- const u = getFirebaseAuth()?.currentUser;
144
- return !!(u && !u.isAnonymous);
145
- },
146
- showAuthModal,
147
- });
148
-
149
- // Get initial user ID (sync - no waiting)
150
- const initialUserId = getCurrentUserId(getFirebaseAuth);
151
-
152
- /**
153
- * Non-blocking initialization (fire and forget)
154
- * RevenueCat best practice: Don't block on initialization.
155
- * The CustomerInfoUpdateListener will handle state updates reactively.
156
- */
157
- const initializeInBackground = async (userId?: string) => {
158
- try {
159
- await SubscriptionManager.initialize(userId);
160
- if (__DEV__) console.log('[SubscriptionInitializer] Background init complete');
161
- } catch (error) {
162
- // Non-critical - listener will handle updates
163
- if (__DEV__) console.log('[SubscriptionInitializer] Background init error (non-critical):', error);
164
- }
165
- };
166
-
167
- // Start initialization in background (non-blocking)
168
- initializeInBackground(initialUserId);
169
-
170
- // Set up auth state listener for reactive updates
171
- // When user logs in/out, re-initialize with new user ID
172
- setupAuthStateListener(getFirebaseAuth, (newUserId) => {
173
- if (__DEV__) console.log('[SubscriptionInitializer] Auth state changed:', newUserId ? 'logged in' : 'logged out');
174
- initializeInBackground(newUserId);
175
- });
176
- };
@@ -1,133 +0,0 @@
1
- /**
2
- * Subscription Service Implementation
3
- * Database-first subscription management
4
- */
5
-
6
- import { timezoneService } from "@umituz/react-native-design-system";
7
- import type { ISubscriptionService } from "../../application/ports/ISubscriptionService";
8
- import type { ISubscriptionRepository } from "../../application/ports/ISubscriptionRepository";
9
- import type { SubscriptionStatus } from "../../domain/entities/SubscriptionStatus";
10
- import { createDefaultSubscriptionStatus } from "../../domain/entities/SubscriptionStatus";
11
- import {
12
- SubscriptionRepositoryError,
13
- SubscriptionValidationError,
14
- } from "../../domain/errors/SubscriptionError";
15
- import type { SubscriptionConfig } from "../../domain/value-objects/SubscriptionConfig";
16
- import {
17
- activateSubscription,
18
- deactivateSubscription,
19
- safeHandleError,
20
- type ActivationHandlerConfig,
21
- } from "./ActivationHandler";
22
-
23
- export class SubscriptionService implements ISubscriptionService {
24
- private repository: ISubscriptionRepository;
25
- private handlerConfig: ActivationHandlerConfig;
26
-
27
- constructor(config: SubscriptionConfig) {
28
- if (!config.repository) {
29
- throw new SubscriptionValidationError("Repository is required");
30
- }
31
-
32
- this.repository = config.repository;
33
- this.handlerConfig = {
34
- repository: config.repository,
35
- onStatusChanged: config.onStatusChanged,
36
- onError: config.onError,
37
- };
38
- }
39
-
40
- async getSubscriptionStatus(userId: string): Promise<SubscriptionStatus> {
41
- try {
42
- const status = await this.repository.getSubscriptionStatus(userId);
43
- if (!status) {
44
- return createDefaultSubscriptionStatus();
45
- }
46
-
47
- const isValid = this.repository.isSubscriptionValid(status);
48
- if (!isValid && status.isPremium) {
49
- return await this.deactivateSubscription(userId);
50
- }
51
-
52
- return status;
53
- } catch (error) {
54
- await this.handleError(error, "getSubscriptionStatus");
55
- return createDefaultSubscriptionStatus();
56
- }
57
- }
58
-
59
- async isPremium(userId: string): Promise<boolean> {
60
- const status = await this.getSubscriptionStatus(userId);
61
- return this.repository.isSubscriptionValid(status);
62
- }
63
-
64
- async activateSubscription(
65
- userId: string,
66
- productId: string,
67
- expiresAt: string | null
68
- ): Promise<SubscriptionStatus> {
69
- return activateSubscription(
70
- this.handlerConfig,
71
- userId,
72
- productId,
73
- expiresAt
74
- );
75
- }
76
-
77
- async deactivateSubscription(userId: string): Promise<SubscriptionStatus> {
78
- return deactivateSubscription(this.handlerConfig, userId);
79
- }
80
-
81
- async updateSubscriptionStatus(
82
- userId: string,
83
- updates: Partial<SubscriptionStatus>
84
- ): Promise<SubscriptionStatus> {
85
- try {
86
- const updatesWithSync = {
87
- ...updates,
88
- syncedAt: timezoneService.getCurrentISOString(),
89
- };
90
-
91
- const updatedStatus = await this.repository.updateSubscriptionStatus(
92
- userId,
93
- updatesWithSync
94
- );
95
-
96
- if (this.handlerConfig.onStatusChanged) {
97
- try {
98
- await this.handlerConfig.onStatusChanged(userId, updatedStatus);
99
- } catch (error) {
100
- await this.handleError(error, "updateSubscriptionStatus.callback");
101
- }
102
- }
103
-
104
- return updatedStatus;
105
- } catch (error) {
106
- await this.handleError(error, "updateSubscriptionStatus");
107
- throw new SubscriptionRepositoryError("Failed to update subscription");
108
- }
109
- }
110
-
111
- private async handleError(error: unknown, context: string): Promise<void> {
112
- await safeHandleError(this.handlerConfig.onError, error, `SubscriptionService.${context}`);
113
- }
114
- }
115
-
116
- let subscriptionServiceInstance: SubscriptionService | null = null;
117
-
118
- export function initializeSubscriptionService(
119
- config: SubscriptionConfig
120
- ): SubscriptionService {
121
- if (!subscriptionServiceInstance) {
122
- subscriptionServiceInstance = new SubscriptionService(config);
123
- }
124
- return subscriptionServiceInstance;
125
- }
126
-
127
- export function getSubscriptionService(): SubscriptionService | null {
128
- return subscriptionServiceInstance;
129
- }
130
-
131
- export function resetSubscriptionService(): void {
132
- subscriptionServiceInstance = null;
133
- }