@umituz/react-native-subscription 2.27.66 → 2.27.68
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 +1 -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/{revenuecat/presentation → domains/subscription/infrastructure}/hooks/useRevenueCat.ts +1 -1
- package/src/domains/subscription/infrastructure/managers/SubscriptionInternalState.ts +12 -0
- package/src/domains/subscription/infrastructure/managers/SubscriptionManager.ts +110 -0
- package/src/{revenuecat → domains/subscription}/infrastructure/services/PurchaseHandler.ts +1 -1
- package/src/{revenuecat → domains/subscription}/infrastructure/services/RestoreHandler.ts +1 -1
- package/src/{revenuecat → domains/subscription}/infrastructure/services/RevenueCatInitializer.ts +1 -1
- package/src/{revenuecat → domains/subscription}/infrastructure/services/RevenueCatService.ts +2 -2
- 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 +69 -0
- package/src/{infrastructure/services → domains/trial/core}/TrialTypes.ts +1 -1
- package/src/domains/trial/infrastructure/DeviceTrialRepository.ts +30 -0
- package/src/index.ts +28 -59
- package/src/init/createSubscriptionInitModule.ts +1 -1
- package/src/presentation/components/details/PremiumStatusBadge.tsx +1 -3
- package/src/presentation/hooks/feedback/useFeedbackSubmit.ts +1 -1
- package/src/presentation/hooks/index.ts +11 -11
- package/src/shared/application/ports/IRevenueCatService.ts +32 -0
- package/src/shared/infrastructure/SubscriptionEventBus.ts +51 -0
- package/src/application/README.md +0 -50
- package/src/domain/README.md +0 -54
- package/src/domain/entities/README.md +0 -50
- package/src/domain/entities/SubscriptionStatus.test.ts +0 -149
- 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/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/README.md +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,50 +0,0 @@
|
|
|
1
|
-
# Domain Value Objects
|
|
2
|
-
|
|
3
|
-
Value objects for the subscription domain.
|
|
4
|
-
|
|
5
|
-
## Location
|
|
6
|
-
|
|
7
|
-
`src/domain/value-objects/`
|
|
8
|
-
|
|
9
|
-
## Strategy
|
|
10
|
-
|
|
11
|
-
Value objects are immutable objects that represent concepts by their attributes rather than identity. They ensure validity and prevent primitive obsession by providing type-safe, validated representations.
|
|
12
|
-
|
|
13
|
-
## Restrictions
|
|
14
|
-
|
|
15
|
-
### REQUIRED
|
|
16
|
-
|
|
17
|
-
- MUST be immutable (all properties readonly)
|
|
18
|
-
- MUST validate on creation (fail fast)
|
|
19
|
-
- MUST override equality (compare by value, not reference)
|
|
20
|
-
- MUST use for complex attributes (not simple primitives)
|
|
21
|
-
- MUST be small and focused
|
|
22
|
-
|
|
23
|
-
### PROHIBITED
|
|
24
|
-
|
|
25
|
-
- MUST NOT allow mutation after creation
|
|
26
|
-
- MUST NOT use reference equality for comparison
|
|
27
|
-
- MUST NOT contain invalid states
|
|
28
|
-
- MUST NOT have identity-based equality
|
|
29
|
-
|
|
30
|
-
### CRITICAL
|
|
31
|
-
|
|
32
|
-
- Always validate on creation
|
|
33
|
-
- Implement value-based equality comparison
|
|
34
|
-
- Keep value objects small and focused
|
|
35
|
-
- Use only for complex attributes, not simple primitives
|
|
36
|
-
|
|
37
|
-
## AI Agent Guidelines
|
|
38
|
-
|
|
39
|
-
When working with value objects:
|
|
40
|
-
1. Make immutable - all properties readonly
|
|
41
|
-
2. Validate on creation - fail fast
|
|
42
|
-
3. Override equality - compare by value, not reference
|
|
43
|
-
4. Use for complex attributes - don't use for simple primitives
|
|
44
|
-
5. Keep small - value objects should be focused
|
|
45
|
-
|
|
46
|
-
## Related Documentation
|
|
47
|
-
|
|
48
|
-
- [Domain Entities](../entities/README.md)
|
|
49
|
-
- [Domain Errors](../errors/README.md)
|
|
50
|
-
- [Domain Layer](../README.md)
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
# Infrastructure Layer
|
|
2
|
-
|
|
3
|
-
Abonelik sisteminin dış dünya ile iletişimini sağlayan implementations ve repositories içeren katman.
|
|
4
|
-
|
|
5
|
-
## Location
|
|
6
|
-
|
|
7
|
-
`src/infrastructure/`
|
|
8
|
-
|
|
9
|
-
## Strategy
|
|
10
|
-
|
|
11
|
-
Dış servis entegrasyonlarını (Firebase, RevenueCat), veri erişim implementasyonlarını ve karmaşık operasyon yöneticilerini içeren katman. Dependency injection ve test edilebilirlik sağlar.
|
|
12
|
-
|
|
13
|
-
## Restrictions
|
|
14
|
-
|
|
15
|
-
### REQUIRED
|
|
16
|
-
|
|
17
|
-
- MUST implement all port interfaces from application layer
|
|
18
|
-
- MUST handle all errors gracefully and propagate appropriately
|
|
19
|
-
- MUST validate all inputs before processing
|
|
20
|
-
- MUST implement caching for frequently accessed data
|
|
21
|
-
- MUST support logging for debugging and monitoring
|
|
22
|
-
- MUST be testable with mock implementations
|
|
23
|
-
|
|
24
|
-
### PROHIBITED
|
|
25
|
-
|
|
26
|
-
- MUST NOT contain business logic (belongs in domain/application)
|
|
27
|
-
- MUST NOT bypass error handling
|
|
28
|
-
- MUST NOT expose implementation details to other layers
|
|
29
|
-
- MUST NOT create tight coupling with external services
|
|
30
|
-
|
|
31
|
-
### CRITICAL
|
|
32
|
-
|
|
33
|
-
- Always validate inputs before processing
|
|
34
|
-
- Implement proper error handling for all external calls
|
|
35
|
-
- Use dependency injection for all external dependencies
|
|
36
|
-
- Ensure all implementations are mockable for testing
|
|
37
|
-
- Implement retry logic for network operations
|
|
38
|
-
|
|
39
|
-
## AI Agent Guidelines
|
|
40
|
-
|
|
41
|
-
When working with infrastructure layer:
|
|
42
|
-
1. Dependency Injection - repository'leri constructor'da alın
|
|
43
|
-
2. Error Handling - tüm hataları yakalayın ve uygun şekilde handle edin
|
|
44
|
-
3. Caching - sık kullanılan verileri cache'leyin
|
|
45
|
-
4. Validation - girdileri validate edin
|
|
46
|
-
5. Logging - önemli operasyonları log'layın
|
|
47
|
-
6. Testing - mock implementasyonlarla test edilebilir yapın
|
|
48
|
-
7. Retry Logic - network hataları için retry logic ekleyin
|
|
49
|
-
|
|
50
|
-
## Related Documentation
|
|
51
|
-
|
|
52
|
-
- [Application Layer](../application/README.md)
|
|
53
|
-
- [Domain Layer](../domain/README.md)
|
|
54
|
-
- [Repositories](./repositories/README.md)
|
|
55
|
-
- [Services](./services/README.md)
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
# Infrastructure Mappers
|
|
2
|
-
|
|
3
|
-
Data transformation mappers between layers.
|
|
4
|
-
|
|
5
|
-
## Overview
|
|
6
|
-
|
|
7
|
-
This directory contains mapper functions that transform data between different layers (e.g., domain entities to DTOs, external API responses to domain models).
|
|
8
|
-
|
|
9
|
-
## Contents
|
|
10
|
-
|
|
11
|
-
- **subscriptionMapper.ts** - Maps between subscription entities and external formats
|
|
12
|
-
- **creditsMapper.ts** - Maps between credit entities and Firestore documents
|
|
13
|
-
|
|
14
|
-
## Purpose
|
|
15
|
-
|
|
16
|
-
Mappers provide clean separation between layers:
|
|
17
|
-
|
|
18
|
-
## Related
|
|
19
|
-
|
|
20
|
-
- [Models](../models/README.md)
|
|
21
|
-
- [Domain](../../domain/README.md)
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
# Infrastructure Models
|
|
2
|
-
|
|
3
|
-
Data models and schemas used by the infrastructure layer.
|
|
4
|
-
|
|
5
|
-
## Overview
|
|
6
|
-
|
|
7
|
-
This directory contains data models, schemas, and interfaces used by infrastructure implementations.
|
|
8
|
-
|
|
9
|
-
## Contents
|
|
10
|
-
|
|
11
|
-
- **SubscriptionModel.ts** - Subscription data model for persistence/transport
|
|
12
|
-
- **CreditsModel.ts** - Credits data model for Firestore
|
|
13
|
-
|
|
14
|
-
## Purpose
|
|
15
|
-
|
|
16
|
-
Models provide structure for data storage and API communication:
|
|
17
|
-
|
|
18
|
-
- **Validation**: Ensure data integrity
|
|
19
|
-
- **Serialization**: Convert to/from storage formats
|
|
20
|
-
- **Type Safety**: Provide TypeScript interfaces
|
|
21
|
-
|
|
22
|
-
## Related
|
|
23
|
-
|
|
24
|
-
- [Repositories](../repositories/README.md)
|
|
25
|
-
- [Services](../services/README.md)
|
|
26
|
-
- [Domain Entities](../../domain/entities/README.md)
|
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Credits Repository
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { doc, getDoc, runTransaction, serverTimestamp, type Firestore, type Transaction } from "firebase/firestore";
|
|
6
|
-
import { BaseRepository, getFirestore } from "@umituz/react-native-firebase";
|
|
7
|
-
import type { CreditsConfig, CreditsResult, DeductCreditsResult } from "../../domain/entities/Credits";
|
|
8
|
-
import type { UserCreditsDocumentRead, PurchaseSource } from "../models/UserCreditsDocument";
|
|
9
|
-
import { initializeCreditsTransaction, type InitializeCreditsMetadata } from "../services/CreditsInitializer";
|
|
10
|
-
import { detectPackageType } from "../../utils/packageTypeDetector";
|
|
11
|
-
import { getCreditAllocation } from "../../utils/creditMapper";
|
|
12
|
-
import { CreditsMapper } from "../mappers/CreditsMapper";
|
|
13
|
-
import type { RevenueCatData } from "../../domain/types/RevenueCatData";
|
|
14
|
-
|
|
15
|
-
export type { RevenueCatData } from "../../domain/types/RevenueCatData";
|
|
16
|
-
|
|
17
|
-
export class CreditsRepository extends BaseRepository {
|
|
18
|
-
constructor(private config: CreditsConfig) { super(); }
|
|
19
|
-
|
|
20
|
-
private getRef(db: Firestore, userId: string) {
|
|
21
|
-
return this.config.useUserSubcollection
|
|
22
|
-
? doc(db, "users", userId, "credits", "balance")
|
|
23
|
-
: doc(db, this.config.collectionName, userId);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
async getCredits(userId: string): Promise<CreditsResult> {
|
|
27
|
-
const db = getFirestore();
|
|
28
|
-
if (!db) {
|
|
29
|
-
if (__DEV__) console.log("[CreditsRepository] No Firestore instance");
|
|
30
|
-
return { success: false, error: { message: "No DB", code: "DB_ERR" } };
|
|
31
|
-
}
|
|
32
|
-
try {
|
|
33
|
-
const ref = this.getRef(db, userId);
|
|
34
|
-
if (__DEV__) console.log("[CreditsRepository] Fetching credits:", { userId: userId.slice(0, 8), path: ref.path });
|
|
35
|
-
const snap = await getDoc(ref);
|
|
36
|
-
if (!snap.exists()) {
|
|
37
|
-
if (__DEV__) console.log("[CreditsRepository] No credits document found");
|
|
38
|
-
return { success: true, data: undefined };
|
|
39
|
-
}
|
|
40
|
-
const d = snap.data() as UserCreditsDocumentRead;
|
|
41
|
-
const entity = CreditsMapper.toEntity(d);
|
|
42
|
-
if (__DEV__) console.log("[CreditsRepository] Credits fetched:", { credits: entity.credits, limit: entity.creditLimit });
|
|
43
|
-
return { success: true, data: entity };
|
|
44
|
-
} catch (e: unknown) {
|
|
45
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
46
|
-
if (__DEV__) console.error("[CreditsRepository] Fetch error:", message);
|
|
47
|
-
return { success: false, error: { message, code: "FETCH_ERR" } };
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
async initializeCredits(
|
|
52
|
-
userId: string, purchaseId?: string, productId?: string,
|
|
53
|
-
source?: PurchaseSource, revenueCatData?: RevenueCatData
|
|
54
|
-
): Promise<CreditsResult> {
|
|
55
|
-
const db = getFirestore();
|
|
56
|
-
if (!db) return { success: false, error: { message: "No DB", code: "INIT_ERR" } };
|
|
57
|
-
try {
|
|
58
|
-
let cfg = { ...this.config };
|
|
59
|
-
if (productId) {
|
|
60
|
-
const amt = this.config.creditPackageAmounts?.[productId];
|
|
61
|
-
if (amt) cfg = { ...cfg, creditLimit: amt };
|
|
62
|
-
else {
|
|
63
|
-
const packageType = detectPackageType(productId);
|
|
64
|
-
const dynamicLimit = getCreditAllocation(packageType, this.config.packageAllocations);
|
|
65
|
-
if (dynamicLimit !== null) cfg = { ...cfg, creditLimit: dynamicLimit };
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const metadata: InitializeCreditsMetadata = {
|
|
70
|
-
productId, source,
|
|
71
|
-
expirationDate: revenueCatData?.expirationDate,
|
|
72
|
-
willRenew: revenueCatData?.willRenew,
|
|
73
|
-
originalTransactionId: revenueCatData?.originalTransactionId,
|
|
74
|
-
isPremium: revenueCatData?.isPremium,
|
|
75
|
-
periodType: revenueCatData?.periodType,
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
await initializeCreditsTransaction(db, this.getRef(db, userId), cfg, purchaseId, metadata);
|
|
79
|
-
// Re-fetch from Firestore to get the actual stored data with all fields
|
|
80
|
-
const snap = await getDoc(this.getRef(db, userId));
|
|
81
|
-
const fullData = snap.exists() ? snap.data() as UserCreditsDocumentRead : undefined;
|
|
82
|
-
return {
|
|
83
|
-
success: true,
|
|
84
|
-
data: fullData ? CreditsMapper.toEntity(fullData) : undefined,
|
|
85
|
-
};
|
|
86
|
-
} catch (e: unknown) {
|
|
87
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
88
|
-
return { success: false, error: { message, code: "INIT_ERR" } };
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
async deductCredit(userId: string, cost: number = 1): Promise<DeductCreditsResult> {
|
|
93
|
-
const db = getFirestore();
|
|
94
|
-
if (!db) return { success: false, error: { message: "No DB", code: "ERR" } };
|
|
95
|
-
try {
|
|
96
|
-
const remaining = await runTransaction(db, async (tx: Transaction) => {
|
|
97
|
-
const docSnap = await tx.get(this.getRef(db, userId));
|
|
98
|
-
if (!docSnap.exists()) throw new Error("NO_CREDITS");
|
|
99
|
-
const current = docSnap.data().credits as number;
|
|
100
|
-
if (current < cost) throw new Error("CREDITS_EXHAUSTED");
|
|
101
|
-
const updated = current - cost;
|
|
102
|
-
tx.update(this.getRef(db, userId), { credits: updated, lastUpdatedAt: serverTimestamp() });
|
|
103
|
-
return updated;
|
|
104
|
-
});
|
|
105
|
-
return { success: true, remainingCredits: remaining };
|
|
106
|
-
} catch (e: unknown) {
|
|
107
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
108
|
-
const code = message === "NO_CREDITS" || message === "CREDITS_EXHAUSTED" ? message : "DEDUCT_ERR";
|
|
109
|
-
return { success: false, error: { message, code } };
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
async hasCredits(userId: string, cost: number = 1): Promise<boolean> {
|
|
114
|
-
const res = await this.getCredits(userId);
|
|
115
|
-
return !!(res.success && res.data && res.data.credits >= cost);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
async syncExpiredStatus(userId: string): Promise<void> {
|
|
119
|
-
const db = getFirestore();
|
|
120
|
-
if (!db) return;
|
|
121
|
-
try {
|
|
122
|
-
const ref = this.getRef(db, userId);
|
|
123
|
-
const { updateDoc } = await import("firebase/firestore");
|
|
124
|
-
await updateDoc(ref, { isPremium: false, status: "expired", lastUpdatedAt: serverTimestamp() });
|
|
125
|
-
if (__DEV__) console.log("[CreditsRepository] Synced expired status:", userId.slice(0, 8));
|
|
126
|
-
} catch (e) {
|
|
127
|
-
if (__DEV__) console.error("[CreditsRepository] Sync expired failed:", e);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
export const createCreditsRepository = (c: CreditsConfig) => new CreditsRepository(c);
|
|
@@ -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)
|