@umituz/react-native-subscription 2.14.99 → 2.14.100
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/README.md +211 -394
- package/package.json +1 -1
- package/src/application/README.md +46 -225
- package/src/application/ports/README.md +42 -97
- package/src/domain/README.md +36 -384
- package/src/domain/constants/README.md +0 -56
- package/src/domain/entities/README.md +43 -169
- package/src/domain/errors/README.md +33 -287
- package/src/domain/value-objects/README.md +43 -179
- package/src/domains/README.md +50 -238
- package/src/domains/README.md.bak +274 -0
- package/src/domains/config/README.md +93 -383
- package/src/domains/config/domain/README.md +23 -376
- package/src/domains/config/domain/entities/README.md +34 -343
- package/src/domains/paywall/README.md +99 -369
- package/src/domains/paywall/components/README.md +34 -178
- package/src/domains/paywall/entities/README.md +34 -193
- package/src/domains/paywall/hooks/README.md +34 -122
- package/src/domains/wallet/README.md +34 -275
- package/src/domains/wallet/README.md.bak +209 -0
- package/src/domains/wallet/domain/README.md +34 -101
- package/src/domains/wallet/domain/entities/README.md +34 -115
- package/src/domains/wallet/domain/errors/README.md +34 -151
- package/src/domains/wallet/infrastructure/README.md +34 -89
- package/src/domains/wallet/presentation/components/README.md +34 -224
- package/src/domains/wallet/presentation/hooks/README.md +34 -248
- package/src/infrastructure/README.md +37 -496
- package/src/infrastructure/mappers/README.md +0 -13
- package/src/infrastructure/repositories/README.md +74 -360
- package/src/infrastructure/services/README.md +95 -370
- package/src/presentation/README.md +123 -408
- package/src/presentation/README.md.bak +172 -0
- package/src/presentation/components/README.md +151 -179
- package/src/presentation/components/README.md.bak +217 -0
- package/src/presentation/components/details/CreditRow.md +65 -310
- package/src/presentation/components/details/DetailRow.md +63 -255
- package/src/presentation/components/details/PremiumDetailsCard.md +65 -238
- package/src/presentation/components/details/PremiumStatusBadge.md +64 -239
- package/src/presentation/components/details/README.md +97 -447
- package/src/presentation/components/feedback/PaywallFeedbackModal.md +63 -287
- package/src/presentation/components/feedback/README.md +97 -445
- package/src/presentation/components/paywall/PaywallModal.md +66 -416
- package/src/presentation/components/paywall/README.md +50 -186
- package/src/presentation/components/sections/README.md +97 -466
- package/src/presentation/components/sections/SubscriptionSection.md +92 -244
- package/src/presentation/hooks/README.md +154 -741
- package/src/presentation/hooks/useAuthAwarePurchase.md +58 -325
- package/src/presentation/hooks/useAuthGate.md +61 -375
- package/src/presentation/hooks/useAuthSubscriptionSync.md +66 -370
- package/src/presentation/hooks/useCreditChecker.md +73 -378
- package/src/presentation/hooks/useCredits.md +74 -313
- package/src/presentation/hooks/useCredits.md.bak +231 -0
- package/src/presentation/hooks/useCreditsGate.md +66 -318
- package/src/presentation/hooks/useDeductCredit.md +0 -76
- package/src/presentation/hooks/useDevTestCallbacks.md +63 -394
- package/src/presentation/hooks/useFeatureGate.md +105 -150
- package/src/presentation/hooks/useFeatureGate.md.bak +284 -0
- package/src/presentation/hooks/useInitializeCredits.md +64 -430
- package/src/presentation/hooks/usePaywall.md +61 -306
- package/src/presentation/hooks/usePaywallOperations.md +64 -458
- package/src/presentation/hooks/usePaywallVisibility.md +67 -316
- package/src/presentation/hooks/usePremium.md +84 -226
- package/src/presentation/hooks/usePremiumGate.md +60 -395
- package/src/presentation/hooks/usePremiumWithCredits.md +64 -401
- package/src/presentation/hooks/useSubscription.md +66 -422
- package/src/presentation/hooks/useSubscriptionDetails.md +65 -410
- package/src/presentation/hooks/useSubscriptionGate.md +80 -164
- package/src/presentation/hooks/useSubscriptionSettingsConfig.md +66 -346
- package/src/presentation/hooks/useSubscriptionStatus.md +66 -396
- package/src/presentation/hooks/useUserTier.md +63 -328
- package/src/presentation/hooks/useUserTierWithRepository.md +64 -424
- package/src/presentation/screens/README.md +48 -190
- package/src/presentation/types/README.md +0 -16
- package/src/presentation/utils/README.md +0 -21
- package/src/revenuecat/README.md +99 -518
- package/src/revenuecat/application/README.md +35 -150
- package/src/revenuecat/application/ports/README.md +34 -162
- package/src/revenuecat/domain/README.md +42 -141
- package/src/revenuecat/domain/constants/README.md +34 -176
- package/src/revenuecat/domain/entities/README.md +34 -374
- package/src/revenuecat/domain/errors/README.md +47 -191
- package/src/revenuecat/domain/types/README.md +34 -366
- package/src/revenuecat/domain/value-objects/README.md +34 -434
- package/src/revenuecat/infrastructure/README.md +34 -43
- package/src/revenuecat/infrastructure/config/README.md +32 -23
- package/src/revenuecat/infrastructure/handlers/README.md +34 -211
- package/src/revenuecat/infrastructure/managers/README.md +34 -42
- package/src/revenuecat/infrastructure/services/README.md +35 -318
- package/src/revenuecat/infrastructure/utils/README.md +34 -375
- package/src/revenuecat/presentation/README.md +34 -176
- package/src/revenuecat/presentation/hooks/README.md +29 -35
- package/src/utils/README.md +38 -525
|
@@ -1,382 +1,41 @@
|
|
|
1
1
|
# RevenueCat Infrastructure Utils
|
|
2
2
|
|
|
3
|
+
## Location
|
|
3
4
|
Utility functions for RevenueCat operations.
|
|
4
5
|
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
case 'PRODUCT_NOT_AVAILABLE_FOR_PURCHASE':
|
|
40
|
-
return {
|
|
41
|
-
code: 'PRODUCT_NOT_AVAILABLE',
|
|
42
|
-
message: 'Product not available',
|
|
43
|
-
userMessage: 'This product is currently unavailable',
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
default:
|
|
47
|
-
return {
|
|
48
|
-
code: 'UNKNOWN_ERROR',
|
|
49
|
-
message: error.message || 'Unknown error',
|
|
50
|
-
userMessage: 'An error occurred. Please try again.',
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
### Entitlement Extraction
|
|
57
|
-
|
|
58
|
-
Extract entitlement information from customer info.
|
|
59
|
-
|
|
60
|
-
```typescript
|
|
61
|
-
function extractEntitlementInfo(
|
|
62
|
-
customerInfo: CustomerInfo,
|
|
63
|
-
entitlementId: string
|
|
64
|
-
): EntitlementInfo | null {
|
|
65
|
-
const entitlement = customerInfo.entitlements[entitlementId];
|
|
66
|
-
|
|
67
|
-
if (!entitlement) {
|
|
68
|
-
return null;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return {
|
|
72
|
-
identifier: entitlementId,
|
|
73
|
-
isActive: entitlement.isActive,
|
|
74
|
-
willRenew: entitlement.willRenew,
|
|
75
|
-
periodType: entitlement.periodType,
|
|
76
|
-
productId: entitlement.productId,
|
|
77
|
-
latestPurchaseDate: entitlement.latestPurchaseDate,
|
|
78
|
-
originalPurchaseDate: entitlement.originalPurchaseDate,
|
|
79
|
-
expirationDate: entitlement.expirationDate,
|
|
80
|
-
renewAt: entitlement.renewAt,
|
|
81
|
-
isSandbox: entitlement.isSandbox,
|
|
82
|
-
billingIssueDetectedAt: entitlement.billingIssueDetectedAt,
|
|
83
|
-
unsubscribeDetectedAt: entitlement.unsubscribeDetectedAt,
|
|
84
|
-
store: entitlement.store,
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
### Package Filtering
|
|
90
|
-
|
|
91
|
-
Filter packages by type or criteria.
|
|
92
|
-
|
|
93
|
-
```typescript
|
|
94
|
-
function filterPackagesByType(
|
|
95
|
-
offering: Offering,
|
|
96
|
-
packageType: PackageType
|
|
97
|
-
): Package[] {
|
|
98
|
-
return offering.availablePackages.filter(
|
|
99
|
-
pkg => pkg.packageType === packageType
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function getSubscriptionPackages(offering: Offering): Package[] {
|
|
104
|
-
return offering.availablePackages.filter(pkg =>
|
|
105
|
-
['monthly', 'annual', 'weekly'].includes(pkg.packageType)
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function getLifetimePackages(offering: Offering): Package[] {
|
|
110
|
-
return offering.availablePackages.filter(pkg =>
|
|
111
|
-
pkg.packageType === 'lifetime'
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function getSinglePurchasePackages(offering: Offering): Package[] {
|
|
116
|
-
return offering.availablePackages.filter(pkg =>
|
|
117
|
-
pkg.packageType === 'single_purchase'
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
### Price Formatting
|
|
123
|
-
|
|
124
|
-
Format prices for display.
|
|
125
|
-
|
|
126
|
-
```typescript
|
|
127
|
-
function formatPrice(
|
|
128
|
-
price: Price,
|
|
129
|
-
locale?: string
|
|
130
|
-
): string {
|
|
131
|
-
return new Intl.NumberFormat(locale, {
|
|
132
|
-
style: 'currency',
|
|
133
|
-
currency: price.currencyCode,
|
|
134
|
-
}).format(price.amount);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function formatPricePerMonth(
|
|
138
|
-
package: Package,
|
|
139
|
-
locale?: string
|
|
140
|
-
): string {
|
|
141
|
-
const { price, product } = package;
|
|
142
|
-
|
|
143
|
-
if (product.subscriptionPeriod) {
|
|
144
|
-
const { value, unit } = product.subscriptionPeriod;
|
|
145
|
-
|
|
146
|
-
// Calculate monthly equivalent
|
|
147
|
-
let months = 1;
|
|
148
|
-
if (unit === 'week') months = value / 4;
|
|
149
|
-
if (unit === 'month') months = value;
|
|
150
|
-
if (unit === 'year') months = value * 12;
|
|
151
|
-
|
|
152
|
-
const monthlyPrice = price.amount / months;
|
|
153
|
-
return formatPrice(
|
|
154
|
-
{ amount: monthlyPrice, currencyCode: price.currencyCode },
|
|
155
|
-
locale
|
|
156
|
-
);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
return formatPrice(price, locale);
|
|
160
|
-
}
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
### Period Formatting
|
|
164
|
-
|
|
165
|
-
Format subscription periods.
|
|
166
|
-
|
|
167
|
-
```typescript
|
|
168
|
-
function formatPeriod(
|
|
169
|
-
period: SubscriptionPeriod,
|
|
170
|
-
locale = 'en-US'
|
|
171
|
-
): string {
|
|
172
|
-
const formatter = new Intl.RelativeTimeFormat(locale, {
|
|
173
|
-
numeric: 'always',
|
|
174
|
-
});
|
|
175
|
-
return formatter.format(period.value, period.unit);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function getPeriodInMonths(period: SubscriptionPeriod): number {
|
|
179
|
-
switch (period.unit) {
|
|
180
|
-
case 'day': return period.value / 30;
|
|
181
|
-
case 'week': return period.value / 4;
|
|
182
|
-
case 'month': return period.value;
|
|
183
|
-
case 'year': return period.value * 12;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function getPeriodInDays(period: SubscriptionPeriod): number {
|
|
188
|
-
switch (period.unit) {
|
|
189
|
-
case 'day': return period.value;
|
|
190
|
-
case 'week': return period.value * 7;
|
|
191
|
-
case 'month': return period.value * 30;
|
|
192
|
-
case 'year': return period.value * 365;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
### Subscription Status
|
|
198
|
-
|
|
199
|
-
Determine subscription status from entitlement.
|
|
200
|
-
|
|
201
|
-
```typescript
|
|
202
|
-
function getSubscriptionStatus(
|
|
203
|
-
entitlement: EntitlementInfo | null
|
|
204
|
-
): SubscriptionStatus {
|
|
205
|
-
if (!entitlement || !entitlement.isActive) {
|
|
206
|
-
return 'expired';
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
if (entitlement.billingIssueDetectedAt) {
|
|
210
|
-
return 'in_billing_retry';
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
if (entitlement.unsubscribeDetectedAt && !entitlement.willRenew) {
|
|
214
|
-
return 'cancelled';
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
return 'active';
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
function getSubscriptionStatusType(
|
|
221
|
-
entitlement: EntitlementInfo | null
|
|
222
|
-
): 'active' | 'expired' | 'canceled' | 'none' {
|
|
223
|
-
if (!entitlement) {
|
|
224
|
-
return 'none';
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
if (!entitlement.isActive) {
|
|
228
|
-
return 'expired';
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
if (entitlement.unsubscribeDetectedAt && !entitlement.willRenew) {
|
|
232
|
-
return 'canceled';
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
return 'active';
|
|
236
|
-
}
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
### Validation
|
|
240
|
-
|
|
241
|
-
Validate RevenueCat data.
|
|
242
|
-
|
|
243
|
-
```typescript
|
|
244
|
-
function isValidEntitlementId(id: string): boolean {
|
|
245
|
-
const validIds = ['premium', 'pro', 'lifetime'];
|
|
246
|
-
return validIds.includes(id);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
function isValidOffering(offering: Offering | null): boolean {
|
|
250
|
-
return (
|
|
251
|
-
offering !== null &&
|
|
252
|
-
offering.availablePackages.length > 0
|
|
253
|
-
);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
function isValidPackage(pkg: Package | null): boolean {
|
|
257
|
-
return (
|
|
258
|
-
pkg !== null &&
|
|
259
|
-
!!pkg.identifier &&
|
|
260
|
-
!!pkg.product &&
|
|
261
|
-
!!pkg.price
|
|
262
|
-
);
|
|
263
|
-
}
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
### Debug Helpers
|
|
267
|
-
|
|
268
|
-
Helper functions for debugging.
|
|
269
|
-
|
|
270
|
-
```typescript
|
|
271
|
-
function logCustomerInfo(info: CustomerInfo): void {
|
|
272
|
-
if (__DEV__) {
|
|
273
|
-
console.log('[RevenueCat] Customer Info:', {
|
|
274
|
-
userId: info.originalAppUserId,
|
|
275
|
-
activeSubscriptions: info.activeSubscriptions,
|
|
276
|
-
allPurchasedProductIds: info.allPurchasedProductIds,
|
|
277
|
-
entitlements: Object.keys(info.entitlements),
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
function logPurchaseResult(result: PurchaseResult): void {
|
|
283
|
-
if (__DEV__) {
|
|
284
|
-
console.log('[RevenueCat] Purchase Result:', {
|
|
285
|
-
transactionId: result.transaction?.transactionIdentifier,
|
|
286
|
-
productId: result.transaction?.productIdentifier,
|
|
287
|
-
hasPremium: !!result.customerInfo.entitlements.premium?.isActive,
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
function logError(error: PurchasesError): void {
|
|
293
|
-
if (__DEV__) {
|
|
294
|
-
console.error('[RevenueCat] Error:', {
|
|
295
|
-
code: error.code,
|
|
296
|
-
message: error.message,
|
|
297
|
-
readableMessage: error.readableErrorMessage,
|
|
298
|
-
});
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
```
|
|
302
|
-
|
|
303
|
-
## Usage Examples
|
|
304
|
-
|
|
305
|
-
### Using Error Mapping
|
|
306
|
-
|
|
307
|
-
```typescript
|
|
308
|
-
import { mapRevenueCatError } from './utils';
|
|
309
|
-
|
|
310
|
-
try {
|
|
311
|
-
const result = await purchasePackage(pkg);
|
|
312
|
-
} catch (error) {
|
|
313
|
-
const domainError = mapRevenueCatError(error);
|
|
314
|
-
|
|
315
|
-
// Show user-friendly message
|
|
316
|
-
Alert.alert('Error', domainError.userMessage);
|
|
317
|
-
|
|
318
|
-
// Log technical details
|
|
319
|
-
console.error(domainError.message);
|
|
320
|
-
}
|
|
321
|
-
```
|
|
322
|
-
|
|
323
|
-
### Extracting Entitlement Info
|
|
324
|
-
|
|
325
|
-
```typescript
|
|
326
|
-
import { extractEntitlementInfo } from './utils';
|
|
327
|
-
|
|
328
|
-
const customerInfo = await getCustomerInfo();
|
|
329
|
-
const premium = extractEntitlementInfo(customerInfo, 'premium');
|
|
330
|
-
|
|
331
|
-
if (premium?.isActive) {
|
|
332
|
-
console.log('Premium active until', premium.expirationDate);
|
|
333
|
-
}
|
|
334
|
-
```
|
|
335
|
-
|
|
336
|
-
### Filtering Packages
|
|
337
|
-
|
|
338
|
-
```typescript
|
|
339
|
-
import { filterPackagesByType, getSubscriptionPackages } from './utils';
|
|
340
|
-
|
|
341
|
-
const offering = await getOfferings();
|
|
342
|
-
const monthlyPackages = filterPackagesByType(offering.current, 'monthly');
|
|
343
|
-
const allSubscriptions = getSubscriptionPackages(offering.current);
|
|
344
|
-
```
|
|
345
|
-
|
|
346
|
-
### Formatting Prices
|
|
347
|
-
|
|
348
|
-
```typescript
|
|
349
|
-
import { formatPrice, formatPricePerMonth } from './utils';
|
|
350
|
-
|
|
351
|
-
// Standard price
|
|
352
|
-
const priceString = formatPrice(pkg.price, 'en-US'); // '$9.99'
|
|
353
|
-
const priceStringTR = formatPrice(pkg.price, 'tr-TR'); // '9,99 $'
|
|
354
|
-
|
|
355
|
-
// Per month (for annual)
|
|
356
|
-
const perMonth = formatPricePerMonth(annualPackage, 'en-US'); // '$8.33/mo'
|
|
357
|
-
```
|
|
358
|
-
|
|
359
|
-
### Getting Subscription Status
|
|
360
|
-
|
|
361
|
-
```typescript
|
|
362
|
-
import { getSubscriptionStatus, getSubscriptionStatusType } from './utils';
|
|
363
|
-
|
|
364
|
-
const entitlement = customerInfo.entitlements.premium;
|
|
365
|
-
const status = getSubscriptionStatus(entitlement); // 'active'
|
|
366
|
-
const statusType = getSubscriptionStatusType(entitlement); // 'active'
|
|
367
|
-
```
|
|
368
|
-
|
|
369
|
-
## Best Practices
|
|
370
|
-
|
|
371
|
-
1. **Error Handling**: Always use error mapping for user-facing messages
|
|
372
|
-
2. **Type Safety**: Ensure types are validated before use
|
|
373
|
-
3. **Locale**: Respect user locale for formatting
|
|
374
|
-
4. **Null Checks**: Always check for null/undefined values
|
|
375
|
-
5. **Logging**: Use debug helpers in development
|
|
376
|
-
6. **Validation**: Validate data before processing
|
|
377
|
-
|
|
378
|
-
## Related
|
|
379
|
-
|
|
6
|
+
## Strategy
|
|
7
|
+
This directory contains utility functions for common RevenueCat operations including error mapping, data transformation, validation, and formatting with proper type safety.
|
|
8
|
+
|
|
9
|
+
## Restrictions
|
|
10
|
+
|
|
11
|
+
### REQUIRED
|
|
12
|
+
- Must use error mapping for user-facing messages
|
|
13
|
+
- Must ensure types are validated before use
|
|
14
|
+
- Must respect user locale for formatting
|
|
15
|
+
- Must check for null/undefined values
|
|
16
|
+
|
|
17
|
+
### PROHIBITED
|
|
18
|
+
- DO NOT show SDK errors directly to users
|
|
19
|
+
- DO NOT use data without type validation
|
|
20
|
+
- DO NOT ignore locale settings
|
|
21
|
+
- DO NOT skip null checks
|
|
22
|
+
|
|
23
|
+
### CRITICAL SAFETY
|
|
24
|
+
- All errors MUST be mapped to domain errors
|
|
25
|
+
- All types MUST be validated before use
|
|
26
|
+
- Locale MUST be respected for formatting
|
|
27
|
+
- Null checks MUST be performed consistently
|
|
28
|
+
|
|
29
|
+
## AI Agent Guidelines
|
|
30
|
+
1. Always map SDK errors to domain errors for user-facing messages
|
|
31
|
+
2. Ensure type safety by validating types before use
|
|
32
|
+
3. Respect user locale when formatting prices and periods
|
|
33
|
+
4. Always check for null/undefined values before processing
|
|
34
|
+
5. Use debug helpers in development for troubleshooting
|
|
35
|
+
6. Validate all data before processing
|
|
36
|
+
7. Test utility functions with edge cases
|
|
37
|
+
|
|
38
|
+
## Related Documentation
|
|
380
39
|
- [RevenueCat Infrastructure](../README.md)
|
|
381
40
|
- [RevenueCat Domain Types](../../domain/types/README.md)
|
|
382
41
|
- [RevenueCat Errors](../../domain/errors/README.md)
|
|
@@ -1,183 +1,41 @@
|
|
|
1
1
|
# RevenueCat Presentation
|
|
2
2
|
|
|
3
|
+
## Location
|
|
3
4
|
Presentation layer for RevenueCat integration.
|
|
4
5
|
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const result = await service.getOfferings();
|
|
39
|
-
setOfferings(result);
|
|
40
|
-
} catch (err) {
|
|
41
|
-
setError(err as Error);
|
|
42
|
-
} finally {
|
|
43
|
-
setLoading(false);
|
|
44
|
-
}
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
return { offerings, loading, error, refetch: loadOfferings };
|
|
48
|
-
}
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
### Components
|
|
52
|
-
|
|
53
|
-
React components for displaying RevenueCat data.
|
|
54
|
-
|
|
55
|
-
**Potential Components:**
|
|
56
|
-
- `PackageList` - Display available packages
|
|
57
|
-
- `PackageCard` - Display individual package
|
|
58
|
-
- `EntitlementBadge` - Show entitlement status
|
|
59
|
-
- `SubscriptionStatus` - Display subscription status
|
|
60
|
-
|
|
61
|
-
**Example:**
|
|
62
|
-
```typescript
|
|
63
|
-
function PackageCard({
|
|
64
|
-
package,
|
|
65
|
-
onPress,
|
|
66
|
-
highlight = false,
|
|
67
|
-
}: {
|
|
68
|
-
package: Package;
|
|
69
|
-
onPress: () => void;
|
|
70
|
-
highlight?: boolean;
|
|
71
|
-
}) {
|
|
72
|
-
return (
|
|
73
|
-
<TouchableOpacity
|
|
74
|
-
onPress={onPress}
|
|
75
|
-
style={[styles.card, highlight && styles.highlight]}
|
|
76
|
-
>
|
|
77
|
-
<Text style={styles.title}>
|
|
78
|
-
{package.product.title}
|
|
79
|
-
</Text>
|
|
80
|
-
<Text style={styles.price}>
|
|
81
|
-
{package.localizedPriceString}
|
|
82
|
-
</Text>
|
|
83
|
-
{package.product.description && (
|
|
84
|
-
<Text style={styles.description}>
|
|
85
|
-
{package.product.description}
|
|
86
|
-
</Text>
|
|
87
|
-
)}
|
|
88
|
-
</TouchableOpacity>
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
## Usage Patterns
|
|
94
|
-
|
|
95
|
-
### Displaying Offerings
|
|
96
|
-
|
|
97
|
-
```typescript
|
|
98
|
-
import { useRevenueCatOfferings } from './hooks/useRevenueCatOfferings';
|
|
99
|
-
import { PackageCard } from './components/PackageCard';
|
|
100
|
-
|
|
101
|
-
function PremiumPackages() {
|
|
102
|
-
const { offerings, loading, error } = useRevenueCatOfferings();
|
|
103
|
-
|
|
104
|
-
if (loading) return <LoadingSpinner />;
|
|
105
|
-
if (error) return <Error message={error.message} />;
|
|
106
|
-
if (!offerings?.current) return <EmptyState />;
|
|
107
|
-
|
|
108
|
-
return (
|
|
109
|
-
<ScrollView horizontal>
|
|
110
|
-
{offerings.current.availablePackages.map(pkg => (
|
|
111
|
-
<PackageCard
|
|
112
|
-
key={pkg.identifier}
|
|
113
|
-
package={pkg}
|
|
114
|
-
onPress={() => handlePurchase(pkg)}
|
|
115
|
-
highlight={pkg.packageType === 'annual'}
|
|
116
|
-
/>
|
|
117
|
-
))}
|
|
118
|
-
</ScrollView>
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
### Checking Entitlements
|
|
124
|
-
|
|
125
|
-
```typescript
|
|
126
|
-
import { useRevenueCatCustomerInfo } from './hooks/useRevenueCatCustomerInfo';
|
|
127
|
-
|
|
128
|
-
function PremiumContent() {
|
|
129
|
-
const { customerInfo, loading } = useRevenueCatCustomerInfo();
|
|
130
|
-
|
|
131
|
-
if (loading) return <LoadingSpinner />;
|
|
132
|
-
|
|
133
|
-
const hasPremium = customerInfo?.entitlements.premium?.isActive ?? false;
|
|
134
|
-
|
|
135
|
-
if (!hasPremium) {
|
|
136
|
-
return <UpgradePrompt />;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
return <PremiumFeatures />;
|
|
140
|
-
}
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
### Purchase Flow
|
|
144
|
-
|
|
145
|
-
```typescript
|
|
146
|
-
import { useRevenueCatPurchase } from './hooks/useRevenueCatPurchase';
|
|
147
|
-
|
|
148
|
-
function PurchaseButton({ package }: { package: Package }) {
|
|
149
|
-
const { purchase, purchasing } = useRevenueCatPurchase();
|
|
150
|
-
|
|
151
|
-
const handlePurchase = async () => {
|
|
152
|
-
const result = await purchase(package);
|
|
153
|
-
|
|
154
|
-
if (result.error) {
|
|
155
|
-
Alert.alert('Purchase Failed', result.error.message);
|
|
156
|
-
} else {
|
|
157
|
-
Alert.alert('Success', 'Purchase completed!');
|
|
158
|
-
}
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
return (
|
|
162
|
-
<Button onPress={handlePurchase} disabled={purchasing}>
|
|
163
|
-
{purchasing ? 'Purchasing...' : 'Subscribe'}
|
|
164
|
-
</Button>
|
|
165
|
-
);
|
|
166
|
-
}
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
## Best Practices
|
|
170
|
-
|
|
171
|
-
1. **Loading States**: Always show loading states during async operations
|
|
172
|
-
2. **Error Handling**: Handle and display errors appropriately
|
|
173
|
-
3. **Optimistic Updates**: Update UI optimistically where possible
|
|
174
|
-
4. **Caching**: Cache customer info and offerings
|
|
175
|
-
5. **Reactivity**: Re-render on entitlement changes
|
|
176
|
-
6. **User Feedback**: Provide clear feedback during purchases
|
|
177
|
-
7. **Validation**: Validate data before displaying
|
|
178
|
-
|
|
179
|
-
## Related
|
|
180
|
-
|
|
6
|
+
## Strategy
|
|
7
|
+
This directory contains React hooks, components, and utilities for integrating RevenueCat functionality into the UI layer with proper loading states, error handling, and caching.
|
|
8
|
+
|
|
9
|
+
## Restrictions
|
|
10
|
+
|
|
11
|
+
### REQUIRED
|
|
12
|
+
- Must show loading states during async operations
|
|
13
|
+
- Must handle and display errors appropriately
|
|
14
|
+
- Must cache customer info and offerings
|
|
15
|
+
- Must re-render on entitlement changes
|
|
16
|
+
|
|
17
|
+
### PROHIBITED
|
|
18
|
+
- DO NOT skip loading states
|
|
19
|
+
- DO NOT show technical errors to users
|
|
20
|
+
- DO NOT ignore cache invalidation
|
|
21
|
+
- DO NOT prevent re-renders on state changes
|
|
22
|
+
|
|
23
|
+
### CRITICAL SAFETY
|
|
24
|
+
- All async operations MUST show loading states
|
|
25
|
+
- All errors MUST be handled and displayed appropriately
|
|
26
|
+
- Data MUST be cached appropriately
|
|
27
|
+
- UI MUST re-render on entitlement changes
|
|
28
|
+
|
|
29
|
+
## AI Agent Guidelines
|
|
30
|
+
1. Always show loading states during async operations
|
|
31
|
+
2. Handle and display errors appropriately to users
|
|
32
|
+
3. Update UI optimistically where possible
|
|
33
|
+
4. Cache customer info and offerings appropriately
|
|
34
|
+
5. Re-render on entitlement changes
|
|
35
|
+
6. Provide clear feedback during purchases
|
|
36
|
+
7. Validate data before displaying to users
|
|
37
|
+
|
|
38
|
+
## Related Documentation
|
|
181
39
|
- [RevenueCat Integration](../README.md)
|
|
182
40
|
- [RevenueCat Application](../application/README.md)
|
|
183
41
|
- [RevenueCat Infrastructure](../infrastructure/README.md)
|