@umituz/react-native-subscription 2.14.97 → 2.14.99
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/LICENSE +21 -0
- package/README.md +461 -0
- package/package.json +1 -3
- package/src/application/README.md +229 -0
- package/src/application/ports/README.md +103 -0
- package/src/domain/README.md +402 -0
- package/src/domain/constants/README.md +80 -0
- package/src/domain/entities/README.md +176 -0
- package/src/domain/errors/README.md +307 -0
- package/src/domain/value-objects/README.md +186 -0
- package/src/domains/README.md +240 -0
- package/src/domains/config/README.md +390 -0
- package/src/domains/config/domain/README.md +390 -0
- package/src/domains/config/domain/entities/README.md +350 -0
- package/src/domains/paywall/README.md +371 -0
- package/src/domains/paywall/components/PaywallHeader.tsx +8 -11
- package/src/domains/paywall/components/README.md +185 -0
- package/src/domains/paywall/entities/README.md +199 -0
- package/src/domains/paywall/hooks/README.md +129 -0
- package/src/domains/wallet/README.md +292 -0
- package/src/domains/wallet/domain/README.md +108 -0
- package/src/domains/wallet/domain/entities/README.md +122 -0
- package/src/domains/wallet/domain/errors/README.md +157 -0
- package/src/domains/wallet/infrastructure/README.md +96 -0
- package/src/domains/wallet/presentation/components/BalanceCard.tsx +6 -12
- package/src/domains/wallet/presentation/components/README.md +231 -0
- package/src/domains/wallet/presentation/hooks/README.md +255 -0
- package/src/infrastructure/README.md +514 -0
- package/src/infrastructure/mappers/README.md +34 -0
- package/src/infrastructure/models/README.md +26 -0
- package/src/infrastructure/repositories/README.md +385 -0
- package/src/infrastructure/services/README.md +374 -0
- package/src/presentation/README.md +410 -0
- package/src/presentation/components/README.md +183 -0
- package/src/presentation/components/details/CreditRow.md +337 -0
- package/src/presentation/components/details/DetailRow.md +283 -0
- package/src/presentation/components/details/PremiumDetailsCard.md +266 -0
- package/src/presentation/components/details/PremiumStatusBadge.md +266 -0
- package/src/presentation/components/details/README.md +449 -0
- package/src/presentation/components/feedback/PaywallFeedbackModal.md +314 -0
- package/src/presentation/components/feedback/README.md +447 -0
- package/src/presentation/components/paywall/PaywallModal.md +444 -0
- package/src/presentation/components/paywall/README.md +190 -0
- package/src/presentation/components/sections/README.md +468 -0
- package/src/presentation/components/sections/SubscriptionSection.md +246 -0
- package/src/presentation/hooks/README.md +743 -0
- package/src/presentation/hooks/useAuthAwarePurchase.md +359 -0
- package/src/presentation/hooks/useAuthGate.md +403 -0
- package/src/presentation/hooks/useAuthSubscriptionSync.md +398 -0
- package/src/presentation/hooks/useCreditChecker.md +407 -0
- package/src/presentation/hooks/useCredits.md +342 -0
- package/src/presentation/hooks/useCreditsGate.md +346 -0
- package/src/presentation/hooks/useDeductCredit.md +176 -0
- package/src/presentation/hooks/useDevTestCallbacks.md +422 -0
- package/src/presentation/hooks/useFeatureGate.md +157 -0
- package/src/presentation/hooks/useInitializeCredits.md +458 -0
- package/src/presentation/hooks/usePaywall.md +334 -0
- package/src/presentation/hooks/usePaywallOperations.md +486 -0
- package/src/presentation/hooks/usePaywallVisibility.md +344 -0
- package/src/presentation/hooks/usePremium.md +230 -0
- package/src/presentation/hooks/usePremiumGate.md +423 -0
- package/src/presentation/hooks/usePremiumWithCredits.md +429 -0
- package/src/presentation/hooks/useSubscription.md +450 -0
- package/src/presentation/hooks/useSubscriptionDetails.md +438 -0
- package/src/presentation/hooks/useSubscriptionGate.md +168 -0
- package/src/presentation/hooks/useSubscriptionSettingsConfig.md +374 -0
- package/src/presentation/hooks/useSubscriptionStatus.md +424 -0
- package/src/presentation/hooks/useUserTier.md +356 -0
- package/src/presentation/hooks/useUserTierWithRepository.md +452 -0
- package/src/presentation/screens/README.md +194 -0
- package/src/presentation/types/README.md +38 -0
- package/src/presentation/utils/README.md +52 -0
- package/src/revenuecat/README.md +523 -0
- package/src/revenuecat/application/README.md +158 -0
- package/src/revenuecat/application/ports/README.md +169 -0
- package/src/revenuecat/domain/README.md +147 -0
- package/src/revenuecat/domain/constants/README.md +183 -0
- package/src/revenuecat/domain/entities/README.md +382 -0
- package/src/revenuecat/domain/errors/README.md +197 -0
- package/src/revenuecat/domain/types/README.md +373 -0
- package/src/revenuecat/domain/value-objects/README.md +441 -0
- package/src/revenuecat/infrastructure/README.md +50 -0
- package/src/revenuecat/infrastructure/config/README.md +40 -0
- package/src/revenuecat/infrastructure/handlers/README.md +218 -0
- package/src/revenuecat/infrastructure/managers/README.md +49 -0
- package/src/revenuecat/infrastructure/services/README.md +325 -0
- package/src/revenuecat/infrastructure/utils/README.md +382 -0
- package/src/revenuecat/presentation/README.md +184 -0
- package/src/revenuecat/presentation/hooks/README.md +56 -0
- package/src/utils/README.md +529 -0
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
# RevenueCat Infrastructure Utils
|
|
2
|
+
|
|
3
|
+
Utility functions for RevenueCat operations.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This directory contains utility functions for common RevenueCat operations including error mapping, data transformation, and validation.
|
|
8
|
+
|
|
9
|
+
## Utilities
|
|
10
|
+
|
|
11
|
+
### Error Mapping
|
|
12
|
+
|
|
13
|
+
Convert RevenueCat SDK errors to domain errors.
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
function mapRevenueCatError(error: PurchasesError): DomainError {
|
|
17
|
+
switch (error.code) {
|
|
18
|
+
case 'PURCHASE_CANCELLED':
|
|
19
|
+
return {
|
|
20
|
+
code: 'PURCHASE_CANCELLED',
|
|
21
|
+
message: 'Purchase was cancelled',
|
|
22
|
+
userMessage: 'You cancelled the purchase',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
case 'NETWORK_ERROR':
|
|
26
|
+
return {
|
|
27
|
+
code: 'NETWORK_ERROR',
|
|
28
|
+
message: 'Network error occurred',
|
|
29
|
+
userMessage: 'Please check your internet connection',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
case 'INVALID_CREDENTIALS_ERROR':
|
|
33
|
+
return {
|
|
34
|
+
code: 'CONFIGURATION_ERROR',
|
|
35
|
+
message: 'Invalid RevenueCat credentials',
|
|
36
|
+
userMessage: 'Configuration error. Please contact support.',
|
|
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
|
+
|
|
380
|
+
- [RevenueCat Infrastructure](../README.md)
|
|
381
|
+
- [RevenueCat Domain Types](../../domain/types/README.md)
|
|
382
|
+
- [RevenueCat Errors](../../domain/errors/README.md)
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# RevenueCat Presentation
|
|
2
|
+
|
|
3
|
+
Presentation layer for RevenueCat integration.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This directory contains React hooks, components, and utilities for integrating RevenueCat functionality into the UI layer.
|
|
8
|
+
|
|
9
|
+
## Components
|
|
10
|
+
|
|
11
|
+
This directory may contain:
|
|
12
|
+
|
|
13
|
+
### Hooks
|
|
14
|
+
|
|
15
|
+
Custom React hooks for RevenueCat operations.
|
|
16
|
+
|
|
17
|
+
**Potential Hooks:**
|
|
18
|
+
- `useRevenueCatCustomerInfo` - Fetch and monitor customer info
|
|
19
|
+
- `useRevenueCatOfferings` - Fetch available offerings
|
|
20
|
+
- `useRevenueCatPurchase` - Handle purchase flow
|
|
21
|
+
- `useRevenueCatEntitlement` - Check entitlement status
|
|
22
|
+
|
|
23
|
+
**Example:**
|
|
24
|
+
```typescript
|
|
25
|
+
function useRevenueCatOfferings() {
|
|
26
|
+
const [offerings, setOfferings] = useState<Offerings | null>(null);
|
|
27
|
+
const [loading, setLoading] = useState(true);
|
|
28
|
+
const [error, setError] = useState<Error | null>(null);
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
loadOfferings();
|
|
32
|
+
}, []);
|
|
33
|
+
|
|
34
|
+
const loadOfferings = async () => {
|
|
35
|
+
try {
|
|
36
|
+
setLoading(true);
|
|
37
|
+
const service = getRevenueCatService();
|
|
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
|
+
|
|
181
|
+
- [RevenueCat Integration](../README.md)
|
|
182
|
+
- [RevenueCat Application](../application/README.md)
|
|
183
|
+
- [RevenueCat Infrastructure](../infrastructure/README.md)
|
|
184
|
+
- [RevenueCat Domain](../domain/README.md)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# RevenueCat Presentation Hooks
|
|
2
|
+
|
|
3
|
+
React hooks for accessing RevenueCat data and operations.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This directory contains React hooks that expose RevenueCat functionality to the presentation layer.
|
|
8
|
+
|
|
9
|
+
## Contents
|
|
10
|
+
|
|
11
|
+
- **useCustomerInfo.ts** - Hook for accessing RevenueCat customer info
|
|
12
|
+
- **useSubscriptionPackages.ts** - Hook for accessing subscription packages/offering
|
|
13
|
+
|
|
14
|
+
## Key Hooks
|
|
15
|
+
|
|
16
|
+
### useCustomerInfo
|
|
17
|
+
|
|
18
|
+
Access RevenueCat customer information including entitlements.
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
function useCustomerInfo() {
|
|
22
|
+
const { customerInfo, isLoading } = useCustomerInfo();
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<View>
|
|
26
|
+
<Text>Entitlements: {Object.keys(customerInfo.entitlements.active).join(', ')}</Text>
|
|
27
|
+
</View>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### useSubscriptionPackages
|
|
33
|
+
|
|
34
|
+
Access available subscription packages from current offering.
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
function PackageList() {
|
|
38
|
+
const { packages, isLoading } = useSubscriptionPackages();
|
|
39
|
+
|
|
40
|
+
if (isLoading) return <Loading />;
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<View>
|
|
44
|
+
{packages.map(pkg => (
|
|
45
|
+
<PackageCard key={pkg.identifier} package={pkg} />
|
|
46
|
+
))}
|
|
47
|
+
</View>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Related
|
|
53
|
+
|
|
54
|
+
- [Main Hooks](../../../presentation/hooks/README.md)
|
|
55
|
+
- [Domain](../../domain/README.md)
|
|
56
|
+
- [Services](../infrastructure/services/README.md)
|