@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
@@ -0,0 +1,51 @@
1
+ type EventCallback<T = any> = (data: T) => void;
2
+
3
+ /**
4
+ * Simple EventBus Implementation
5
+ * Used to decouple services and provide an observer pattern for subscription events.
6
+ */
7
+ export class SubscriptionEventBus {
8
+ private static instance: SubscriptionEventBus;
9
+ private listeners: Record<string, EventCallback[]> = {};
10
+
11
+ private constructor() {}
12
+
13
+ static getInstance(): SubscriptionEventBus {
14
+ if (!SubscriptionEventBus.instance) {
15
+ SubscriptionEventBus.instance = new SubscriptionEventBus();
16
+ }
17
+ return SubscriptionEventBus.instance;
18
+ }
19
+
20
+ on<T>(event: string, callback: EventCallback<T>): () => void {
21
+ if (!this.listeners[event]) {
22
+ this.listeners[event] = [];
23
+ }
24
+ this.listeners[event].push(callback);
25
+
26
+ // Return unsubscribe function
27
+ return () => {
28
+ this.listeners[event] = this.listeners[event].filter(l => l !== callback);
29
+ };
30
+ }
31
+
32
+ emit<T>(event: string, data: T): void {
33
+ if (!this.listeners[event]) return;
34
+ this.listeners[event].forEach(callback => {
35
+ try {
36
+ callback(data);
37
+ } catch (error) {
38
+ if (__DEV__) console.error(`[SubscriptionEventBus] Error in listener for ${event}:`, error);
39
+ }
40
+ });
41
+ }
42
+ }
43
+
44
+ export const subscriptionEventBus = SubscriptionEventBus.getInstance();
45
+
46
+ export const SUBSCRIPTION_EVENTS = {
47
+ CREDITS_UPDATED: "credits_updated",
48
+ PURCHASE_COMPLETED: "purchase_completed",
49
+ RENEWAL_DETECTED: "renewal_detected",
50
+ PREMIUM_STATUS_CHANGED: "premium_status_changed",
51
+ };
@@ -7,23 +7,22 @@ import { PACKAGE_TYPE, type PackageType } from "../domain/entities/SubscriptionC
7
7
 
8
8
  export type SubscriptionPackageType = PackageType;
9
9
 
10
+ /**
11
+ * Check if identifier is a credit package (consumable purchase)
12
+ * Credit packages use a different system and don't need type detection
13
+ */
10
14
  /**
11
15
  * Check if identifier is a credit package (consumable purchase)
12
16
  * Credit packages use a different system and don't need type detection
13
17
  */
14
18
  function isCreditPackage(identifier: string): boolean {
15
- return identifier.includes("credit");
19
+ // Matches "credit" as a word or part of a common naming pattern
20
+ return /\bcredit\b|_credit_|-credit-/i.test(identifier) || identifier.toLowerCase().includes("credit");
16
21
  }
17
22
 
18
23
  /**
19
24
  * Detect package type from product identifier
20
- * Supports common RevenueCat naming patterns:
21
- * - premium_weekly, weekly_premium, premium-weekly
22
- * - premium_monthly, monthly_premium, premium-monthly
23
- * - premium_yearly, yearly_premium, premium-yearly, premium_annual, annual_premium
24
- * - preview-product-id (Preview API mode in Expo Go)
25
- *
26
- * Note: Credit packages (consumable purchases) are skipped silently
25
+ * Supports common RevenueCat naming patterns with regex for better accuracy
27
26
  */
28
27
  export function detectPackageType(productIdentifier: string): SubscriptionPackageType {
29
28
  if (!productIdentifier) {
@@ -45,28 +44,24 @@ export function detectPackageType(productIdentifier: string): SubscriptionPackag
45
44
  return PACKAGE_TYPE.MONTHLY;
46
45
  }
47
46
 
48
- // Weekly detection
49
- if (normalized.includes("weekly") || normalized.includes("week")) {
47
+ // Weekly detection: matches "weekly" or "week" as distinct parts of the ID
48
+ if (/\bweekly?\b|_week_|-week-|\.week\./i.test(normalized)) {
50
49
  if (__DEV__) {
51
50
  console.log("[PackageTypeDetector] Detected: WEEKLY");
52
51
  }
53
52
  return PACKAGE_TYPE.WEEKLY;
54
53
  }
55
54
 
56
- // Monthly detection
57
- if (normalized.includes("monthly") || normalized.includes("month")) {
55
+ // Monthly detection: matches "monthly" or "month"
56
+ if (/\bmonthly?\b|_month_|-month-|\.month\./i.test(normalized)) {
58
57
  if (__DEV__) {
59
58
  console.log("[PackageTypeDetector] Detected: MONTHLY");
60
59
  }
61
60
  return PACKAGE_TYPE.MONTHLY;
62
61
  }
63
62
 
64
- // Yearly detection (includes annual)
65
- if (
66
- normalized.includes("yearly") ||
67
- normalized.includes("year") ||
68
- normalized.includes("annual")
69
- ) {
63
+ // Yearly detection: matches "yearly", "year", or "annual"
64
+ if (/\byearly?\b|_year_|-year-|\.year\.|annual/i.test(normalized)) {
70
65
  if (__DEV__) {
71
66
  console.log("[PackageTypeDetector] Detected: YEARLY");
72
67
  }
@@ -1,50 +0,0 @@
1
- # Application Layer
2
-
3
- Abonelik uygulamasının iş mantığını ve servis kontratlarını içeren katman.
4
-
5
- ## Location
6
-
7
- `src/application/`
8
-
9
- ## Strategy
10
-
11
- Ortak kullanım durumlarını gerçekleştiren, servis kontratlarını tanımlayan ve uygulama kurallarını yöneten katman. Dependency injection ve test edilebilirlik sağlar.
12
-
13
- ## Restrictions
14
-
15
- ### REQUIRED
16
-
17
- - MUST use dependency injection pattern
18
- - MUST define service contracts through interfaces
19
- - MUST implement error handling and propagation
20
- - MUST ensure type safety for all operations
21
- - MUST support dependency inversion principle
22
-
23
- ### PROHIBITED
24
-
25
- - MUST NOT contain direct infrastructure dependencies
26
- - MUST NOT bypass repository interfaces
27
- - MUST NOT leak implementation details
28
- - MUST NOT mix business logic with presentation
29
-
30
- ### CRITICAL
31
-
32
- - Keep interfaces small and focused (Interface Segregation)
33
- - Maintain single responsibility for each service
34
- - Always handle errors appropriately and propagate upward
35
- - Ensure all operations are type-safe
36
-
37
- ## AI Agent Guidelines
38
-
39
- When working with application layer:
40
- 1. Interface Segregation - küçük, odaklanmış interface'ler tanımlayın
41
- 2. Dependency Inversion - high-level modüller low-level modüllere bağımlı olmamalı
42
- 3. Single Responsibility - her service/repository tek bir sorumluluğa sahip olmalı
43
- 4. Error Handling - hataları uygun şekilde handle edin ve yukarı propagation edin
44
- 5. Testing - interface'ler mock ile kolay test edilebilir
45
-
46
- ## Related Documentation
47
-
48
- - [Domain Layer](../domain/README.md)
49
- - [Infrastructure Layer](../infrastructure/README.md)
50
- - [Ports](./ports/README.md)
@@ -1,50 +0,0 @@
1
- # Domain Entities
2
-
3
- Core domain entities for subscription management.
4
-
5
- ## Location
6
-
7
- `src/domain/entities/`
8
-
9
- ## Strategy
10
-
11
- Domain entities represent the core business concepts and rules of the subscription system. They are framework-agnostic and contain only business logic, ensuring pure domain modeling.
12
-
13
- ## Restrictions
14
-
15
- ### REQUIRED
16
-
17
- - MUST validate themselves on creation
18
- - MUST remain immutable after creation
19
- - MUST encapsulate business logic internally
20
- - MUST use value objects for complex attributes
21
- - MUST be framework-agnostic
22
-
23
- ### PROHIBITED
24
-
25
- - MUST NOT have framework dependencies
26
- - MUST NOT expose internal state directly
27
- - MUST NOT allow direct state modification
28
- - MUST NOT contain infrastructure concerns
29
-
30
- ### CRITICAL
31
-
32
- - Always validate invariants to ensure valid state
33
- - Prevent direct state modification through immutability
34
- - Keep business rules encapsulated within entities
35
- - Maintain purity - no external dependencies
36
-
37
- ## AI Agent Guidelines
38
-
39
- When working with domain entities:
40
- 1. Keep entities pure - no framework dependencies
41
- 2. Validate invariants - ensure valid state
42
- 3. Use value objects - for complex attributes
43
- 4. Encapsulate logic - keep business rules inside entities
44
- 5. Make immutable - prevent direct state modification
45
-
46
- ## Related Documentation
47
-
48
- - [Value Objects](../value-objects/README.md)
49
- - [Domain Errors](../errors/README.md)
50
- - [Domain Layer](../README.md)
@@ -1,105 +0,0 @@
1
- import {
2
- createDefaultSubscriptionStatus,
3
- isSubscriptionValid,
4
- calculateDaysRemaining,
5
- } from './SubscriptionStatus';
6
-
7
- describe('SubscriptionStatus', () => {
8
- describe('createDefaultSubscriptionStatus', () => {
9
- it('should create default subscription status', () => {
10
- const status = createDefaultSubscriptionStatus();
11
-
12
- expect(status).toEqual({
13
- isPremium: false,
14
- expiresAt: null,
15
- productId: null,
16
- purchasedAt: null,
17
- customerId: null,
18
- syncedAt: null,
19
- status: 'none',
20
- });
21
- });
22
- });
23
-
24
- describe('isSubscriptionValid', () => {
25
- it('should return false for null status', () => {
26
- expect(isSubscriptionValid(null)).toBe(false);
27
- });
28
-
29
- it('should return false for non-premium status', () => {
30
- const status = {
31
- isPremium: false,
32
- expiresAt: null,
33
- productId: null,
34
- purchasedAt: null,
35
- customerId: null,
36
- syncedAt: null,
37
- };
38
-
39
- expect(isSubscriptionValid(status)).toBe(false);
40
- });
41
-
42
- it('should return true for lifetime subscription', () => {
43
- const status = {
44
- isPremium: true,
45
- expiresAt: null,
46
- productId: 'lifetime',
47
- purchasedAt: '2024-01-01T00:00:00.000Z',
48
- customerId: 'customer123',
49
- syncedAt: '2024-01-01T00:00:00.000Z',
50
- };
51
-
52
- expect(isSubscriptionValid(status)).toBe(true);
53
- });
54
-
55
- it('should return true for active subscription', () => {
56
- const futureDate = new Date();
57
- futureDate.setDate(futureDate.getDate() + 30);
58
-
59
- const status = {
60
- isPremium: true,
61
- expiresAt: futureDate.toISOString(),
62
- productId: 'monthly',
63
- purchasedAt: '2024-01-01T00:00:00.000Z',
64
- customerId: 'customer123',
65
- syncedAt: '2024-01-01T00:00:00.000Z',
66
- };
67
-
68
- expect(isSubscriptionValid(status)).toBe(true);
69
- });
70
-
71
- it('should return false for expired subscription', () => {
72
- const pastDate = new Date();
73
- pastDate.setDate(pastDate.getDate() - 1);
74
-
75
- const status = {
76
- isPremium: true,
77
- expiresAt: pastDate.toISOString(),
78
- productId: 'monthly',
79
- purchasedAt: '2024-01-01T00:00:00.000Z',
80
- customerId: 'customer123',
81
- syncedAt: '2024-01-01T00:00:00.000Z',
82
- };
83
-
84
- expect(isSubscriptionValid(status)).toBe(false);
85
- });
86
- });
87
-
88
- describe('calculateDaysRemaining', () => {
89
- it('should return null for null input', () => {
90
- expect(calculateDaysRemaining(null)).toBeNull();
91
- });
92
-
93
- it('should return positive days for future expiration', () => {
94
- const futureDate = new Date();
95
- futureDate.setDate(futureDate.getDate() + 5);
96
- expect(calculateDaysRemaining(futureDate.toISOString())).toBe(5);
97
- });
98
-
99
- it('should return 0 for past expiration', () => {
100
- const pastDate = new Date();
101
- pastDate.setDate(pastDate.getDate() - 5);
102
- expect(calculateDaysRemaining(pastDate.toISOString())).toBe(0);
103
- });
104
- });
105
- });
@@ -1,53 +0,0 @@
1
- # Domain Errors
2
-
3
- Domain-specific error types for subscription system.
4
-
5
- ## Location
6
-
7
- `src/domain/errors/`
8
-
9
- ## Strategy
10
-
11
- Domain errors provide typed, contextual error handling for business logic failures. They make error handling explicit and type-safe, enabling precise error management.
12
-
13
- ## Restrictions
14
-
15
- ### REQUIRED
16
-
17
- - MUST use specific error types (not generic Error)
18
- - MUST include contextual information
19
- - MUST document all error codes
20
- - MUST handle errors gracefully with user-friendly messages
21
- - MUST log errors for debugging
22
- - MUST use type guards for type-safe error handling
23
-
24
- ### PROHIBITED
25
-
26
- - MUST NOT swallow errors without handling
27
- - MUST NOT use generic Error class
28
- - MUST NOT expose sensitive information in error messages
29
- - MUST NOT ignore error conditions
30
-
31
- ### CRITICAL
32
-
33
- - Always handle or rethrow errors
34
- - Include relevant context in error objects
35
- - Use type guards to enable type-safe error handling
36
- - Show user-friendly messages while logging technical details
37
-
38
- ## AI Agent Guidelines
39
-
40
- When working with domain errors:
41
- 1. Use specific error types - don't use generic Error
42
- 2. Include context - add relevant data to errors
43
- 3. Document error codes - list all possible errors
44
- 4. Handle gracefully - show user-friendly messages
45
- 5. Log errors - track for debugging
46
- 6. Don't swallow errors - always handle or rethrow
47
- 7. Use type guards - enable type-safe error handling
48
-
49
- ## Related Documentation
50
-
51
- - [Domain Entities](../entities/README.md)
52
- - [Value Objects](../value-objects/README.md)
53
- - [Domain Layer](../README.md)
@@ -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);