@umituz/react-native-subscription 2.15.3 → 2.15.4
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/presentation/hooks/useSubscriptionSettingsConfig.ts +6 -2
- package/src/revenuecat/infrastructure/utils/ExpirationDateCalculator.ts +43 -12
- package/src/domains/README.md.bak +0 -274
- package/src/domains/wallet/README.md.bak +0 -209
- package/src/presentation/README.md.bak +0 -172
- package/src/presentation/components/README.md.bak +0 -217
- package/src/presentation/hooks/useCredits.md.bak +0 -231
- package/src/presentation/hooks/useFeatureGate.md.bak +0 -284
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-subscription",
|
|
3
|
-
"version": "2.15.
|
|
3
|
+
"version": "2.15.4",
|
|
4
4
|
"description": "Complete subscription management with RevenueCat, paywall UI, and credits system for React Native apps",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -16,6 +16,7 @@ import { useCreditsArray, getSubscriptionStatusType } from "./useSubscriptionSet
|
|
|
16
16
|
import { getCreditsConfig } from "../../infrastructure/repositories/CreditsRepositoryProvider";
|
|
17
17
|
import { detectPackageType } from "../../utils/packageTypeDetector";
|
|
18
18
|
import { getCreditAllocation } from "../../utils/creditMapper";
|
|
19
|
+
import { getExpirationDate } from "../../revenuecat/infrastructure/utils/ExpirationDateCalculator";
|
|
19
20
|
import type {
|
|
20
21
|
SubscriptionSettingsConfig,
|
|
21
22
|
SubscriptionStatusType,
|
|
@@ -75,8 +76,11 @@ export const useSubscriptionSettingsConfig = (
|
|
|
75
76
|
}, [premiumEntitlement?.productIdentifier, creditLimit]);
|
|
76
77
|
|
|
77
78
|
// Get expiration date from RevenueCat entitlement (source of truth)
|
|
78
|
-
//
|
|
79
|
-
const entitlementExpirationDate =
|
|
79
|
+
// Apply sandbox-to-production conversion for better testing UX
|
|
80
|
+
const entitlementExpirationDate = useMemo(() => {
|
|
81
|
+
if (!premiumEntitlement) return null;
|
|
82
|
+
return getExpirationDate(premiumEntitlement);
|
|
83
|
+
}, [premiumEntitlement]);
|
|
80
84
|
|
|
81
85
|
// Prefer CustomerInfo expiration (real-time) over cached status
|
|
82
86
|
const expiresAtIso = entitlementExpirationDate || (statusExpirationDate
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Expiration Date Calculator
|
|
3
3
|
* Handles RevenueCat expiration date extraction with edge case handling
|
|
4
|
+
* Includes sandbox-to-production date conversion for better testing UX
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
7
|
import type { RevenueCatEntitlement } from '../../domain/types/RevenueCatTypes';
|
|
7
8
|
import { detectPackageType, type SubscriptionPackageType } from '../../../utils/packageTypeDetector';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
|
-
* Add subscription period to a date
|
|
11
|
+
* Add production subscription period to a date
|
|
11
12
|
*/
|
|
12
|
-
function
|
|
13
|
+
function addProductionPeriod(date: Date, packageType: SubscriptionPackageType): Date {
|
|
13
14
|
const newDate = new Date(date);
|
|
14
15
|
|
|
15
16
|
switch (packageType) {
|
|
@@ -23,7 +24,6 @@ function addSubscriptionPeriod(date: Date, packageType: SubscriptionPackageType)
|
|
|
23
24
|
newDate.setDate(newDate.getDate() + 365);
|
|
24
25
|
break;
|
|
25
26
|
default:
|
|
26
|
-
// Unknown type, default to monthly
|
|
27
27
|
newDate.setDate(newDate.getDate() + 30);
|
|
28
28
|
break;
|
|
29
29
|
}
|
|
@@ -32,18 +32,48 @@ function addSubscriptionPeriod(date: Date, packageType: SubscriptionPackageType)
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
|
-
*
|
|
35
|
+
* Convert sandbox expiration to production-equivalent expiration
|
|
36
36
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
37
|
+
* Apple Sandbox durations:
|
|
38
|
+
* - Weekly: 3 minutes → 7 days
|
|
39
|
+
* - Monthly: 5 minutes → 30 days
|
|
40
|
+
* - Yearly: 1 hour → 365 days
|
|
39
41
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
+
* For better testing UX, we calculate what the expiration would be in production
|
|
43
|
+
* based on the latest purchase date.
|
|
44
|
+
*/
|
|
45
|
+
function convertSandboxToProduction(
|
|
46
|
+
latestPurchaseDate: string | null,
|
|
47
|
+
productIdentifier: string
|
|
48
|
+
): string {
|
|
49
|
+
const purchaseDate = latestPurchaseDate
|
|
50
|
+
? new Date(latestPurchaseDate)
|
|
51
|
+
: new Date();
|
|
52
|
+
|
|
53
|
+
const packageType = detectPackageType(productIdentifier);
|
|
54
|
+
const productionExpiration = addProductionPeriod(purchaseDate, packageType);
|
|
55
|
+
|
|
56
|
+
return productionExpiration.toISOString();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Adjust expiration date for edge cases
|
|
61
|
+
*
|
|
62
|
+
* Handles two scenarios:
|
|
63
|
+
* 1. Sandbox mode: Convert to production-equivalent dates for better UX
|
|
64
|
+
* 2. Same-day expiration bug: Add period if expiration is in the past
|
|
42
65
|
*/
|
|
43
66
|
function adjustExpirationDate(
|
|
44
67
|
expirationDate: string,
|
|
45
|
-
productIdentifier: string
|
|
68
|
+
productIdentifier: string,
|
|
69
|
+
isSandbox: boolean,
|
|
70
|
+
latestPurchaseDate: string | null
|
|
46
71
|
): string {
|
|
72
|
+
// In sandbox mode, show production-equivalent expiration for better testing UX
|
|
73
|
+
if (isSandbox && __DEV__) {
|
|
74
|
+
return convertSandboxToProduction(latestPurchaseDate, productIdentifier);
|
|
75
|
+
}
|
|
76
|
+
|
|
47
77
|
const expDate = new Date(expirationDate);
|
|
48
78
|
const now = new Date();
|
|
49
79
|
|
|
@@ -54,7 +84,7 @@ function adjustExpirationDate(
|
|
|
54
84
|
|
|
55
85
|
// If expiration is today or past, add subscription period
|
|
56
86
|
const packageType = detectPackageType(productIdentifier);
|
|
57
|
-
const adjustedDate =
|
|
87
|
+
const adjustedDate = addProductionPeriod(expDate, packageType);
|
|
58
88
|
|
|
59
89
|
return adjustedDate.toISOString();
|
|
60
90
|
}
|
|
@@ -70,9 +100,10 @@ export function getExpirationDate(
|
|
|
70
100
|
return null;
|
|
71
101
|
}
|
|
72
102
|
|
|
73
|
-
// Apply edge case fix for same-day expirations
|
|
74
103
|
return adjustExpirationDate(
|
|
75
104
|
entitlement.expirationDate,
|
|
76
|
-
entitlement.productIdentifier
|
|
105
|
+
entitlement.productIdentifier,
|
|
106
|
+
entitlement.isSandbox ?? false,
|
|
107
|
+
entitlement.latestPurchaseDate ?? null
|
|
77
108
|
);
|
|
78
109
|
}
|
|
@@ -1,274 +0,0 @@
|
|
|
1
|
-
# Domains
|
|
2
|
-
|
|
3
|
-
Specialized domain modules implementing specific business logic and features.
|
|
4
|
-
|
|
5
|
-
## Location
|
|
6
|
-
|
|
7
|
-
**Directory**: `src/domains/`
|
|
8
|
-
|
|
9
|
-
**Type**: Domain Collection
|
|
10
|
-
|
|
11
|
-
## Strategy
|
|
12
|
-
|
|
13
|
-
### Domain-Driven Design
|
|
14
|
-
|
|
15
|
-
This directory implements Domain-Driven Design (DDD) principles:
|
|
16
|
-
|
|
17
|
-
1. **Wallet Domain**: Credit balance, transactions, and purchase flow
|
|
18
|
-
2. **Paywall Domain**: Upgrade prompts, subscription management
|
|
19
|
-
3. **Config Domain**: Feature flags, subscription configuration
|
|
20
|
-
|
|
21
|
-
Each domain is self-contained with:
|
|
22
|
-
- **Domain Layer**: Business logic, entities, value objects
|
|
23
|
-
- **Infrastructure Layer**: External integrations, repositories
|
|
24
|
-
- **Presentation Layer**: Domain-specific hooks and components
|
|
25
|
-
|
|
26
|
-
### Architecture Pattern
|
|
27
|
-
|
|
28
|
-
```
|
|
29
|
-
┌─────────────────────────────────────┐
|
|
30
|
-
│ Application Layer │
|
|
31
|
-
│ (Use Cases, Orchestration) │
|
|
32
|
-
└──────────────┬──────────────────────┘
|
|
33
|
-
│
|
|
34
|
-
┌───────┴────────┐
|
|
35
|
-
│ │
|
|
36
|
-
┌──────▼──────┐ ┌─────▼──────┐
|
|
37
|
-
│ Wallet │ │ Paywall │
|
|
38
|
-
│ Domain │ │ Domain │
|
|
39
|
-
├─────────────┤ ├────────────┤
|
|
40
|
-
│ Domain │ │ Domain │
|
|
41
|
-
│ Infra │ │ Infra │
|
|
42
|
-
│ Presentation│ │ Presentation│
|
|
43
|
-
└──────┬──────┘ └─────┬──────┘
|
|
44
|
-
│ │
|
|
45
|
-
└────────┬───────┘
|
|
46
|
-
│
|
|
47
|
-
┌────────▼──────────┐
|
|
48
|
-
│ Shared Infra │
|
|
49
|
-
│ (Firebase, etc) │
|
|
50
|
-
└───────────────────┘
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
## Domain Modules
|
|
54
|
-
|
|
55
|
-
### Wallet Domain (`wallet/`)
|
|
56
|
-
|
|
57
|
-
**Responsibility**: Credit balance and transaction management
|
|
58
|
-
|
|
59
|
-
**Key Features**:
|
|
60
|
-
- Credit balance tracking
|
|
61
|
-
- Transaction history
|
|
62
|
-
- Purchase initialization
|
|
63
|
-
- Real-time updates
|
|
64
|
-
|
|
65
|
-
**Documentation**: `wallet/README.md`
|
|
66
|
-
|
|
67
|
-
### Paywall Domain (`paywall/`)
|
|
68
|
-
|
|
69
|
-
**Responsibility**: Subscription upgrade flows and paywall UI
|
|
70
|
-
|
|
71
|
-
**Key Features**:
|
|
72
|
-
- Paywall display logic
|
|
73
|
-
- Subscription management
|
|
74
|
-
- Feature gating
|
|
75
|
-
- Upgrade prompts
|
|
76
|
-
|
|
77
|
-
**Documentation**: `paywall/README.md`
|
|
78
|
-
|
|
79
|
-
### Config Domain (`config/`)
|
|
80
|
-
|
|
81
|
-
**Responsibility**: Subscription configuration and feature flags
|
|
82
|
-
|
|
83
|
-
**Key Features**:
|
|
84
|
-
- Subscription tiers
|
|
85
|
-
- Feature configuration
|
|
86
|
-
- Pricing rules
|
|
87
|
-
- Feature flags
|
|
88
|
-
|
|
89
|
-
**Documentation**: `config/README.md`
|
|
90
|
-
|
|
91
|
-
## Restrictions
|
|
92
|
-
|
|
93
|
-
### REQUIRED
|
|
94
|
-
|
|
95
|
-
- **Domain Isolation**: Domains MUST NOT directly depend on each other
|
|
96
|
-
- **Interface Segregation**: Use well-defined interfaces between layers
|
|
97
|
-
- **Dependency Inversion**: Depend on abstractions, not concretions
|
|
98
|
-
- **Testability**: All domains MUST be testable in isolation
|
|
99
|
-
|
|
100
|
-
### PROHIBITED
|
|
101
|
-
|
|
102
|
-
- **NEVER** share domain logic between domains (use shared kernel if needed)
|
|
103
|
-
- **NEVER** create circular dependencies between domains
|
|
104
|
-
- **DO NOT** bypass domain layer from presentation
|
|
105
|
-
- **NEVER** expose infrastructure details to other domains
|
|
106
|
-
|
|
107
|
-
### CRITICAL SAFETY
|
|
108
|
-
|
|
109
|
-
- **ALWAYS** validate invariants at domain boundaries
|
|
110
|
-
- **ALWAYS** implement domain errors for business rule violations
|
|
111
|
-
- **NEVER** allow inconsistent domain state
|
|
112
|
-
- **MUST** implement proper transaction boundaries
|
|
113
|
-
- **ALWAYS** sanitize inputs from external sources
|
|
114
|
-
|
|
115
|
-
## Rules
|
|
116
|
-
|
|
117
|
-
### Domain Boundaries
|
|
118
|
-
|
|
119
|
-
```typescript
|
|
120
|
-
// CORRECT - Respecting domain boundaries
|
|
121
|
-
// Wallet domain handles credits
|
|
122
|
-
const { credits } = useCredits(); // From wallet domain
|
|
123
|
-
|
|
124
|
-
// Paywall domain handles upgrades
|
|
125
|
-
const { showPaywall } = usePaywallOperations(); // From paywall domain
|
|
126
|
-
|
|
127
|
-
// INCORRECT - Crossing domain boundaries
|
|
128
|
-
const walletRepository = new WalletRepository();
|
|
129
|
-
// Directly using wallet repo in paywall component
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
### Domain Errors
|
|
133
|
-
|
|
134
|
-
```typescript
|
|
135
|
-
// CORRECT - Domain-specific errors
|
|
136
|
-
class InsufficientCreditsError extends DomainError {
|
|
137
|
-
constructor(required: number, available: number) {
|
|
138
|
-
super(`Insufficient credits: need ${required}, have ${available}`);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// INCORRECT - Generic errors
|
|
143
|
-
throw new Error('Not enough credits'); // Loses domain context
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
### Dependency Direction
|
|
147
|
-
|
|
148
|
-
```typescript
|
|
149
|
-
// CORRECT - Dependency inversion
|
|
150
|
-
interface ICreditsRepository {
|
|
151
|
-
getBalance(userId: string): Promise<number>;
|
|
152
|
-
deductCredits(userId: string, amount: number): Promise<void>;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Domain depends on interface, not implementation
|
|
156
|
-
class CreditService {
|
|
157
|
-
constructor(private repo: ICreditsRepository) {}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// INCORRECT - Concrete dependency
|
|
161
|
-
class CreditService {
|
|
162
|
-
constructor(private repo: FirebaseCreditsRepository) {}
|
|
163
|
-
// Tightly coupled to Firebase
|
|
164
|
-
}
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
## AI Agent Guidelines
|
|
168
|
-
|
|
169
|
-
### When Working with Domains
|
|
170
|
-
|
|
171
|
-
1. **Always** respect domain boundaries
|
|
172
|
-
2. **Always** use dependency inversion
|
|
173
|
-
3. **Always** implement domain-specific errors
|
|
174
|
-
4. **Always** validate invariants at boundaries
|
|
175
|
-
5. **Never** create circular dependencies
|
|
176
|
-
|
|
177
|
-
### Integration Checklist
|
|
178
|
-
|
|
179
|
-
- [ ] Identify correct domain for feature
|
|
180
|
-
- [ ] Respect domain boundaries
|
|
181
|
-
- [ ] Use appropriate interfaces
|
|
182
|
-
- [ ] Handle domain errors
|
|
183
|
-
- [ ] Test domain in isolation
|
|
184
|
-
- [ ] Document domain interactions
|
|
185
|
-
- [ ] Validate invariants
|
|
186
|
-
- [ ] Implement transaction boundaries
|
|
187
|
-
- [ ] Test cross-domain scenarios
|
|
188
|
-
- [ ] Verify no circular dependencies
|
|
189
|
-
|
|
190
|
-
### Common Patterns
|
|
191
|
-
|
|
192
|
-
1. **Aggregate Root**: Single entry point for aggregate
|
|
193
|
-
2. **Value Objects**: Immutable values with no identity
|
|
194
|
-
3. **Domain Events**: Publish domain events for side effects
|
|
195
|
-
4. **Repositories**: Abstract data access
|
|
196
|
-
5. **Factories**: Complex object creation
|
|
197
|
-
6. **Domain Services**: Business logic that doesn't fit entities
|
|
198
|
-
7. **Specification**: Business rule encapsulation
|
|
199
|
-
8. **Anti-Corruption Layer**: Isolate from external systems
|
|
200
|
-
|
|
201
|
-
## Related Documentation
|
|
202
|
-
|
|
203
|
-
- **Wallet Domain**: `wallet/README.md`
|
|
204
|
-
- **Paywall Domain**: `paywall/README.md`
|
|
205
|
-
- **Config Domain**: `config/README.md`
|
|
206
|
-
- **Domain Layer**: `../domain/README.md`
|
|
207
|
-
- **Infrastructure**: `../infrastructure/README.md`
|
|
208
|
-
|
|
209
|
-
## Domain Structure
|
|
210
|
-
|
|
211
|
-
```
|
|
212
|
-
src/domains/
|
|
213
|
-
├── wallet/ # Wallet and credits domain
|
|
214
|
-
│ ├── domain/ # Business logic
|
|
215
|
-
│ ├── infrastructure/ # External integrations
|
|
216
|
-
│ └── presentation/ # UI hooks and components
|
|
217
|
-
├── paywall/ # Paywall and upgrades domain
|
|
218
|
-
│ ├── domain/
|
|
219
|
-
│ ├── infrastructure/
|
|
220
|
-
│ └── presentation/
|
|
221
|
-
└── config/ # Configuration domain
|
|
222
|
-
├── domain/
|
|
223
|
-
├── infrastructure/
|
|
224
|
-
└── presentation/
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
## Creating a New Domain
|
|
228
|
-
|
|
229
|
-
When creating a new domain:
|
|
230
|
-
|
|
231
|
-
1. **Define Boundaries**: What is the domain's responsibility?
|
|
232
|
-
2. **Identify Entities**: What are the core business objects?
|
|
233
|
-
3. **Define Invariants**: What rules must always be true?
|
|
234
|
-
4. **Design Interfaces**: How will other layers interact?
|
|
235
|
-
5. **Implement Repository**: Abstract data access
|
|
236
|
-
6. **Create Presentation Layer**: Hooks and components
|
|
237
|
-
7. **Write Tests**: Test domain logic in isolation
|
|
238
|
-
8. **Document**: Provide comprehensive README
|
|
239
|
-
|
|
240
|
-
Example:
|
|
241
|
-
|
|
242
|
-
```typescript
|
|
243
|
-
// Domain entity
|
|
244
|
-
export class FeatureFlag {
|
|
245
|
-
constructor(
|
|
246
|
-
public readonly id: string,
|
|
247
|
-
public readonly name: string,
|
|
248
|
-
private _isEnabled: boolean
|
|
249
|
-
) {}
|
|
250
|
-
|
|
251
|
-
get isEnabled(): boolean {
|
|
252
|
-
return this._isEnabled;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
enable(): void {
|
|
256
|
-
this._isEnabled = true;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
disable(): void {
|
|
260
|
-
this._isEnabled = false;
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// Repository interface
|
|
265
|
-
export interface IFeatureFlagRepository {
|
|
266
|
-
findById(id: string): Promise<FeatureFlag | null>;
|
|
267
|
-
save(flag: FeatureFlag): Promise<void>;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// Presentation hook
|
|
271
|
-
export function useFeatureFlag(featureId: string) {
|
|
272
|
-
// Hook implementation
|
|
273
|
-
}
|
|
274
|
-
```
|
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
# Wallet Domain
|
|
2
|
-
|
|
3
|
-
Credit balance management, transaction history tracking, and product metadata management.
|
|
4
|
-
|
|
5
|
-
## Location
|
|
6
|
-
|
|
7
|
-
**Directory**: `src/domains/wallet/`
|
|
8
|
-
|
|
9
|
-
**Type**: Domain
|
|
10
|
-
|
|
11
|
-
## Strategy
|
|
12
|
-
|
|
13
|
-
### Domain Architecture
|
|
14
|
-
|
|
15
|
-
The Wallet Domain implements a layered architecture for credit management:
|
|
16
|
-
|
|
17
|
-
1. **Domain Layer**
|
|
18
|
-
- Entities: `UserCredits`, `Transaction`, `CreditType`
|
|
19
|
-
- Value Objects: `CreditAmount`, `TransactionReason`
|
|
20
|
-
- Errors: `InsufficientCreditsError`, `InvalidCreditAmountError`
|
|
21
|
-
|
|
22
|
-
2. **Infrastructure Layer**
|
|
23
|
-
- Repositories: `CreditsRepository`, `TransactionsRepository`
|
|
24
|
-
- Firebase Services: Data persistence and real-time updates
|
|
25
|
-
- Mappers: Entity ↔ DTO transformations
|
|
26
|
-
|
|
27
|
-
3. **Presentation Layer**
|
|
28
|
-
- Hooks: `useCredits`, `useDeductCredit`, `useInitializeCredits`
|
|
29
|
-
- Components: Credit displays, transaction lists
|
|
30
|
-
|
|
31
|
-
### Credit Flow
|
|
32
|
-
|
|
33
|
-
1. **Initialization**
|
|
34
|
-
- Fetch initial credit balance from backend
|
|
35
|
-
- Subscribe to real-time credit updates
|
|
36
|
-
- Cache in TanStack Query
|
|
37
|
-
|
|
38
|
-
2. **Operations**
|
|
39
|
-
- Check balance before operations
|
|
40
|
-
- Deduct credits optimistically
|
|
41
|
-
- Sync with backend
|
|
42
|
-
- Rollback on failure
|
|
43
|
-
|
|
44
|
-
3. **Transaction History**
|
|
45
|
-
- Record all credit operations
|
|
46
|
-
- Provide audit trail
|
|
47
|
-
- Support pagination and filtering
|
|
48
|
-
|
|
49
|
-
### Integration Points
|
|
50
|
-
|
|
51
|
-
- **Firebase**: Real database for persistence
|
|
52
|
-
- **TanStack Query**: Client-side caching and state management
|
|
53
|
-
- **RevenueCat**: Purchase flow integration
|
|
54
|
-
- **Presentation Hooks**: UI integration layer
|
|
55
|
-
|
|
56
|
-
## Restrictions
|
|
57
|
-
|
|
58
|
-
### REQUIRED
|
|
59
|
-
|
|
60
|
-
- **User Authentication**: All operations require authenticated user
|
|
61
|
-
- **Server Validation**: Credit operations MUST be validated server-side
|
|
62
|
-
- **Transaction Recording**: All credit changes MUST be recorded in transaction history
|
|
63
|
-
- **Error Handling**: MUST handle insufficient credits gracefully
|
|
64
|
-
|
|
65
|
-
### PROHIBITED
|
|
66
|
-
|
|
67
|
-
- **NEVER** allow client-side credit modifications without server validation
|
|
68
|
-
- **NEVER** deduct credits without sufficient balance
|
|
69
|
-
- **DO NOT** expose internal repository logic to UI
|
|
70
|
-
- **NEVER** store credit balance in AsyncStorage (use secure backend)
|
|
71
|
-
|
|
72
|
-
### CRITICAL SAFETY
|
|
73
|
-
|
|
74
|
-
- **ALWAYS** validate credit amounts (must be positive)
|
|
75
|
-
- **ALWAYS** implement optimistic updates with rollback
|
|
76
|
-
- **NEVER** trust client-side credit balance for security decisions
|
|
77
|
-
- **MUST** implement retry logic for failed operations
|
|
78
|
-
- **ALWAYS** sanitize transaction reasons to prevent injection attacks
|
|
79
|
-
|
|
80
|
-
## Rules
|
|
81
|
-
|
|
82
|
-
### Repository Initialization
|
|
83
|
-
|
|
84
|
-
```typescript
|
|
85
|
-
// CORRECT - Proper repository setup
|
|
86
|
-
const creditsRepository = new CreditsRepository({
|
|
87
|
-
firebase: firebaseInstance,
|
|
88
|
-
userId: user.uid,
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
// INCORRECT - Missing userId
|
|
92
|
-
const creditsRepository = new CreditsRepository({
|
|
93
|
-
firebase: firebaseInstance,
|
|
94
|
-
// userId undefined
|
|
95
|
-
});
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
### Credit Operations
|
|
99
|
-
|
|
100
|
-
```typescript
|
|
101
|
-
// CORRECT - Check before deduct
|
|
102
|
-
const hasEnoughCredits = await creditsRepository.checkBalance(requiredAmount);
|
|
103
|
-
if (hasEnoughCredits) {
|
|
104
|
-
await creditsRepository.deductCredits(requiredAmount, 'feature_usage');
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// INCORRECT - Deduct without checking
|
|
108
|
-
await creditsRepository.deductCredits(requiredAmount, 'feature_usage');
|
|
109
|
-
// May throw InsufficientCreditsError
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
### Transaction Recording
|
|
113
|
-
|
|
114
|
-
```typescript
|
|
115
|
-
// CORRECT - Record all operations
|
|
116
|
-
await creditsRepository.deductCredits(
|
|
117
|
-
amount,
|
|
118
|
-
reason,
|
|
119
|
-
{ featureId, metadata } // Include context
|
|
120
|
-
);
|
|
121
|
-
|
|
122
|
-
// INCORRECT - Missing context
|
|
123
|
-
await creditsRepository.deductCredits(amount, reason);
|
|
124
|
-
// Lost audit trail
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
### Error Handling
|
|
128
|
-
|
|
129
|
-
```typescript
|
|
130
|
-
// CORRECT - Handle specific errors
|
|
131
|
-
try {
|
|
132
|
-
await creditsRepository.deductCredits(amount, reason);
|
|
133
|
-
} catch (error) {
|
|
134
|
-
if (error instanceof InsufficientCreditsError) {
|
|
135
|
-
showUpgradePrompt();
|
|
136
|
-
} else if (error instanceof InvalidCreditAmountError) {
|
|
137
|
-
showInvalidAmountError();
|
|
138
|
-
} else {
|
|
139
|
-
showGenericError();
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// INCORRECT - Generic error handling
|
|
144
|
-
try {
|
|
145
|
-
await creditsRepository.deductCredits(amount, reason);
|
|
146
|
-
} catch (error) {
|
|
147
|
-
console.error(error); // Doesn't help user
|
|
148
|
-
}
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
## AI Agent Guidelines
|
|
152
|
-
|
|
153
|
-
### When Implementing Credit Operations
|
|
154
|
-
|
|
155
|
-
1. **Always** check balance before deducting
|
|
156
|
-
2. **Always** provide transaction reason and metadata
|
|
157
|
-
3. **Always** handle insufficient credits gracefully
|
|
158
|
-
4. **Always** implement optimistic updates with rollback
|
|
159
|
-
5. **Never** trust client-side balance for security
|
|
160
|
-
|
|
161
|
-
### Integration Checklist
|
|
162
|
-
|
|
163
|
-
- [ ] Initialize repository with valid userId
|
|
164
|
-
- [ ] Implement error boundaries
|
|
165
|
-
- [ ] Handle loading states
|
|
166
|
-
- [ ] Provide user feedback for all operations
|
|
167
|
-
- [ ] Test with insufficient credits
|
|
168
|
-
- [ ] Test with zero balance
|
|
169
|
-
- [ ] Test transaction history
|
|
170
|
-
- [ ] Test real-time updates
|
|
171
|
-
- [ ] Implement retry logic
|
|
172
|
-
- [ ] Sanitize user inputs
|
|
173
|
-
|
|
174
|
-
### Common Patterns
|
|
175
|
-
|
|
176
|
-
1. **Pre-check**: Always verify balance before operations
|
|
177
|
-
2. **Optimistic Updates**: Update UI immediately, rollback on failure
|
|
178
|
-
3. **Transaction Context**: Include featureId and metadata in all operations
|
|
179
|
-
4. **Error Recovery**: Provide upgrade path when credits insufficient
|
|
180
|
-
5. **Real-time Sync**: Subscribe to credit changes for multi-device sync
|
|
181
|
-
6. **Audit Trail**: Maintain complete transaction history
|
|
182
|
-
7. **Caching**: Use TanStack Query for performance
|
|
183
|
-
8. **Validation**: Validate all inputs on both client and server
|
|
184
|
-
|
|
185
|
-
## Related Documentation
|
|
186
|
-
|
|
187
|
-
- **Credits Repository**: `infrastructure/repositories/README.md`
|
|
188
|
-
- **useCredits Hook**: `../../presentation/hooks/useCredits.md`
|
|
189
|
-
- **useDeductCredit Hook**: `../../presentation/hooks/useDeductCredit.md`
|
|
190
|
-
- **UserCredits Entity**: `domain/entities/README.md`
|
|
191
|
-
- **Transaction Errors**: `domain/errors/README.md`
|
|
192
|
-
|
|
193
|
-
## Domain Structure
|
|
194
|
-
|
|
195
|
-
```
|
|
196
|
-
src/domains/wallet/
|
|
197
|
-
├── domain/
|
|
198
|
-
│ ├── entities/ # Core entities
|
|
199
|
-
│ │ ├── UserCredits.ts
|
|
200
|
-
│ │ └── Transaction.ts
|
|
201
|
-
│ ├── value-objects/ # Value objects
|
|
202
|
-
│ └── errors/ # Domain errors
|
|
203
|
-
├── infrastructure/
|
|
204
|
-
│ ├── repositories/ # Data access
|
|
205
|
-
│ └── services/ # External services
|
|
206
|
-
└── presentation/
|
|
207
|
-
├── hooks/ # React hooks
|
|
208
|
-
└── components/ # UI components
|
|
209
|
-
```
|