@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.
- package/package.json +3 -1
- package/src/domains/credits/application/CreditLimitCalculator.ts +17 -0
- package/src/domains/credits/application/CreditsInitializer.ts +85 -0
- package/src/domains/credits/application/DeductCreditsCommand.ts +52 -0
- package/src/domains/credits/application/PurchaseMetadataGenerator.ts +59 -0
- package/src/domains/credits/application/credit-strategies/CreditAllocationContext.ts +35 -0
- package/src/domains/credits/application/credit-strategies/ICreditStrategy.ts +18 -0
- package/src/domains/credits/application/credit-strategies/StandardPurchaseCreditStrategy.ts +16 -0
- package/src/domains/credits/application/credit-strategies/SyncCreditStrategy.ts +15 -0
- package/src/domains/credits/application/credit-strategies/TrialCreditStrategy.ts +18 -0
- package/src/{infrastructure/mappers → domains/credits/core}/CreditsMapper.ts +4 -4
- package/src/domains/credits/infrastructure/CreditsRepository.ts +102 -0
- package/src/{presentation/hooks → domains/credits/presentation}/useCredits.ts +21 -4
- package/src/domains/subscription/application/SubscriptionAuthListener.ts +26 -0
- package/src/domains/subscription/application/SubscriptionInitializer.ts +77 -0
- package/src/{infrastructure/services → domains/subscription/application}/SubscriptionInitializerTypes.ts +21 -1
- package/src/domains/subscription/application/SubscriptionSyncService.ts +71 -0
- package/src/domains/subscription/application/SubscriptionSyncUtils.ts +16 -0
- package/src/{revenuecat/domain/value-objects → domains/subscription/core}/RevenueCatConfig.ts +1 -1
- package/src/{domain/types → domains/subscription/core}/RevenueCatData.ts +1 -1
- package/src/{domain/entities → domains/subscription/core}/SubscriptionStatus.ts +13 -21
- package/src/domains/subscription/core/SubscriptionStatusHandlers.ts +51 -0
- package/src/domains/subscription/infrastructure/handlers/PackageHandler.ts +67 -0
- package/src/domains/subscription/infrastructure/handlers/PurchaseStatusResolver.ts +27 -0
- package/src/domains/subscription/infrastructure/managers/SubscriptionInternalState.ts +12 -0
- package/src/domains/subscription/infrastructure/managers/SubscriptionManager.ts +110 -0
- package/src/{presentation/hooks → domains/subscription/presentation}/usePremium.ts +7 -4
- package/src/domains/trial/application/TrialEligibilityService.ts +25 -0
- package/src/domains/trial/application/TrialService.ts +68 -0
- package/src/{infrastructure/services → domains/trial/core}/TrialTypes.ts +1 -1
- package/src/domains/trial/infrastructure/DeviceTrialRepository.ts +30 -0
- package/src/presentation/components/details/PremiumStatusBadge.tsx +2 -2
- package/src/presentation/hooks/index.ts +11 -11
- package/src/shared/infrastructure/SubscriptionEventBus.ts +51 -0
- package/src/utils/packageTypeDetector.ts +13 -18
- package/src/application/README.md +0 -50
- package/src/domain/entities/README.md +0 -50
- package/src/domain/entities/SubscriptionStatus.test.ts +0 -105
- package/src/domain/errors/README.md +0 -53
- package/src/domain/value-objects/README.md +0 -50
- package/src/infrastructure/README.md +0 -55
- package/src/infrastructure/mappers/README.md +0 -21
- package/src/infrastructure/models/README.md +0 -26
- package/src/infrastructure/repositories/CreditsRepository.ts +0 -132
- package/src/infrastructure/repositories/README.md +0 -99
- package/src/infrastructure/services/CreditsInitializer.ts +0 -170
- package/src/infrastructure/services/README.md +0 -99
- package/src/infrastructure/services/SubscriptionInitializer.ts +0 -176
- package/src/infrastructure/services/SubscriptionService.ts +0 -133
- package/src/infrastructure/services/TrialService.ts +0 -197
- package/src/infrastructure/services/app-service-helpers.ts +0 -111
- package/src/revenuecat/README.md +0 -104
- package/src/revenuecat/application/README.md +0 -43
- package/src/revenuecat/application/ports/IRevenueCatService.ts +0 -76
- package/src/revenuecat/application/ports/README.md +0 -41
- package/src/revenuecat/domain/README.md +0 -48
- package/src/revenuecat/domain/constants/README.md +0 -41
- package/src/revenuecat/domain/entities/README.md +0 -42
- package/src/revenuecat/domain/errors/README.md +0 -53
- package/src/revenuecat/domain/types/README.md +0 -41
- package/src/revenuecat/domain/value-objects/README.md +0 -41
- package/src/revenuecat/index.ts +0 -13
- package/src/revenuecat/infrastructure/handlers/PackageHandler.ts +0 -161
- package/src/revenuecat/infrastructure/managers/SubscriptionManager.ts +0 -165
- package/src/revenuecat/presentation/README.md +0 -42
- /package/src/{domain/entities → domains/credits/core}/Credits.ts +0 -0
- /package/src/{infrastructure/models → domains/credits/core}/UserCreditsDocument.ts +0 -0
- /package/src/{infrastructure/repositories → domains/credits/infrastructure}/CreditsRepositoryProvider.ts +0 -0
- /package/src/{presentation/hooks → domains/credits/presentation}/useDeductCredit.ts +0 -0
- /package/src/{revenuecat/domain/constants → domains/subscription/core}/RevenueCatConstants.ts +0 -0
- /package/src/{revenuecat/domain/errors → domains/subscription/core}/RevenueCatError.ts +0 -0
- /package/src/{revenuecat/domain/types → domains/subscription/core}/RevenueCatTypes.ts +0 -0
- /package/src/{domain/entities → domains/subscription/core}/SubscriptionConstants.ts +0 -0
- /package/src/{revenuecat → domains/subscription}/infrastructure/README.md +0 -0
- /package/src/{revenuecat → domains/subscription}/infrastructure/config/README.md +0 -0
- /package/src/{revenuecat → domains/subscription}/infrastructure/handlers/README.md +0 -0
- /package/src/{revenuecat/presentation → domains/subscription/infrastructure}/hooks/README.md +0 -0
- /package/src/{revenuecat/presentation → domains/subscription/infrastructure}/hooks/subscriptionQueryKeys.ts +0 -0
- /package/src/{revenuecat/presentation → domains/subscription/infrastructure}/hooks/useCustomerInfo.ts +0 -0
- /package/src/{revenuecat/presentation → domains/subscription/infrastructure}/hooks/useInitializeSubscription.ts +0 -0
- /package/src/{revenuecat/presentation → domains/subscription/infrastructure}/hooks/usePaywallFlow.ts +0 -0
- /package/src/{revenuecat/presentation → domains/subscription/infrastructure}/hooks/usePurchasePackage.ts +0 -0
- /package/src/{revenuecat/presentation → domains/subscription/infrastructure}/hooks/useRestorePurchase.ts +0 -0
- /package/src/{revenuecat/presentation → domains/subscription/infrastructure}/hooks/useRevenueCat.ts +0 -0
- /package/src/{revenuecat/presentation → domains/subscription/infrastructure}/hooks/useRevenueCatTrialEligibility.ts +0 -0
- /package/src/{revenuecat/presentation → domains/subscription/infrastructure}/hooks/useSubscriptionPackages.ts +0 -0
- /package/src/{revenuecat/presentation → domains/subscription/infrastructure}/hooks/useSubscriptionQueries.ts +0 -0
- /package/src/{revenuecat → domains/subscription}/infrastructure/managers/README.md +0 -0
- /package/src/{revenuecat → domains/subscription}/infrastructure/services/CustomerInfoListenerManager.ts +0 -0
- /package/src/{revenuecat → domains/subscription}/infrastructure/services/OfferingsFetcher.ts +0 -0
- /package/src/{revenuecat → domains/subscription}/infrastructure/services/PurchaseHandler.ts +0 -0
- /package/src/{revenuecat → domains/subscription}/infrastructure/services/README.md +0 -0
- /package/src/{revenuecat → domains/subscription}/infrastructure/services/RestoreHandler.ts +0 -0
- /package/src/{revenuecat → domains/subscription}/infrastructure/services/RevenueCatInitializer.ts +0 -0
- /package/src/{revenuecat → domains/subscription}/infrastructure/services/RevenueCatService.ts +0 -0
- /package/src/{revenuecat → domains/subscription}/infrastructure/services/ServiceStateManager.ts +0 -0
- /package/src/{revenuecat → domains/subscription}/infrastructure/utils/ApiKeyResolver.ts +0 -0
- /package/src/{revenuecat → domains/subscription}/infrastructure/utils/InitializationCache.ts +0 -0
- /package/src/{revenuecat → domains/subscription}/infrastructure/utils/PremiumStatusSyncer.ts +0 -0
- /package/src/{revenuecat → domains/subscription}/infrastructure/utils/README.md +0 -0
- /package/src/{revenuecat → domains/subscription}/infrastructure/utils/RenewalDetector.ts +0 -0
- /package/src/{revenuecat → domains/subscription}/infrastructure/utils/UserIdProvider.ts +0 -0
- /package/src/{presentation/hooks → domains/subscription/presentation}/useAuthAwarePurchase.ts +0 -0
- /package/src/{presentation/hooks → domains/subscription/presentation}/useAuthSubscriptionSync.ts +0 -0
- /package/src/{presentation/hooks → domains/subscription/presentation}/useFeatureGate.ts +0 -0
- /package/src/{presentation/hooks → domains/subscription/presentation}/usePaywallVisibility.ts +0 -0
- /package/src/{presentation/hooks → domains/subscription/presentation}/usePremiumGate.ts +0 -0
- /package/src/{presentation/hooks → domains/subscription/presentation}/useSavedPurchaseAutoExecution.ts +0 -0
- /package/src/{presentation/hooks → domains/subscription/presentation}/useSubscriptionSettingsConfig.ts +0 -0
- /package/src/{presentation/hooks → domains/subscription/presentation}/useSubscriptionSettingsConfig.utils.ts +0 -0
- /package/src/{presentation/hooks → domains/subscription/presentation}/useSubscriptionStatus.ts +0 -0
- /package/src/{infrastructure/services → shared/application}/ActivationHandler.ts +0 -0
- /package/src/{infrastructure/services → shared/application}/FeedbackService.ts +0 -0
- /package/src/{application → shared/application}/ports/ISubscriptionRepository.ts +0 -0
- /package/src/{application → shared/application}/ports/ISubscriptionService.ts +0 -0
- /package/src/{application → shared/application}/ports/README.md +0 -0
- /package/src/{domain/errors → shared/utils}/InsufficientCreditsError.ts +0 -0
- /package/src/{infrastructure → shared}/utils/Logger.ts +0 -0
- /package/src/{domain/value-objects → shared/utils}/Result.ts +0 -0
- /package/src/{domain/value-objects → shared/utils}/SubscriptionConfig.ts +0 -0
- /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
|
-
}
|