Перейти к основному содержимому

Subscriptions

Управление подписками через RevenueCat.

Структура

src/features/subscriptions/
├── context/
│ └── subscription-provider.tsx # Контекст подписок
├── services/
│ └── revenuecat.ts # Инициализация SDK
├── hooks/
│ ├── use-offerings.ts # Получить планы
│ ├── use-purchase.ts # Сделать покупку
│ └── use-restore-purchases.ts # Восстановить
├── components/
│ ├── paywall.tsx # Экран подписки
│ ├── tier-card.tsx # Карточка плана
│ └── upgrade-prompt.tsx # Промпт апгрейда
├── constants.ts
├── types.ts
└── index.ts

Тарифные планы

ПланЦенаКурсыAI чатИгры
FREEБесплатно15 msg/день
LEARNER$4.99/мес550 msg/день
PRO$9.99/мес
TEAM$19.99/мес∞ + sharing

RevenueCat Setup

// src/features/subscriptions/services/revenuecat.ts
import Purchases from 'react-native-purchases';
import { Platform } from 'react-native';

export async function initializeRevenueCat(userId: string) {
const apiKey = Platform.select({
ios: process.env.EXPO_PUBLIC_REVENUECAT_IOS_KEY,
android: process.env.EXPO_PUBLIC_REVENUECAT_ANDROID_KEY,
});

await Purchases.configure({
apiKey: apiKey!,
appUserID: userId,
});
}

SubscriptionProvider

// src/features/subscriptions/context/subscription-provider.tsx
export function SubscriptionProvider({ children }) {
const { session } = useSession();
const [customerInfo, setCustomerInfo] = useState<CustomerInfo | null>(null);
const [isLoading, setIsLoading] = useState(true);

useEffect(() => {
if (!session?.user.id) return;

// Инициализация RevenueCat
initializeRevenueCat(session.user.id);

// Получить текущую подписку
Purchases.getCustomerInfo().then(setCustomerInfo);

// Подписаться на изменения
Purchases.addCustomerInfoUpdateListener(setCustomerInfo);

setIsLoading(false);
}, [session?.user.id]);

const tier = useMemo(() => {
if (!customerInfo) return 'FREE';

const entitlements = customerInfo.entitlements.active;
if (entitlements['team']) return 'TEAM';
if (entitlements['pro']) return 'PRO';
if (entitlements['learner']) return 'LEARNER';
return 'FREE';
}, [customerInfo]);

return (
<SubscriptionContext.Provider value={{ tier, customerInfo, isLoading }}>
{children}
</SubscriptionContext.Provider>
);
}

Hooks

useSubscription

import { useSubscription } from '@/features/subscriptions';

function CourseCard({ course }) {
const { tier, canCreateCourse } = useSubscription();

if (!canCreateCourse) {
return <UpgradePrompt />;
}

return <CourseContent course={course} />;
}

useOfferings

Получить доступные планы:

import { useOfferings } from '@/features/subscriptions';

function Paywall() {
const { data: offerings, isLoading } = useOfferings();

return (
<View>
{offerings?.current?.availablePackages.map(pkg => (
<TierCard
key={pkg.identifier}
package={pkg}
onSelect={() => handlePurchase(pkg)}
/>
))}
</View>
);
}

usePurchase

import { usePurchase } from '@/features/subscriptions';

function TierCard({ package: pkg }) {
const { mutate: purchase, isPending } = usePurchase();

const handlePurchase = () => {
purchase(pkg, {
onSuccess: () => {
router.push('/subscription/success');
},
onError: (error) => {
if (!error.userCancelled) {
Alert.alert('Ошибка', error.message);
}
},
});
};

return (
<Button onPress={handlePurchase} disabled={isPending}>
<Text>{pkg.product.priceString}/мес</Text>
</Button>
);
}

useRestorePurchases

import { useRestorePurchases } from '@/features/subscriptions';

function SettingsScreen() {
const { mutate: restore, isPending } = useRestorePurchases();

return (
<Button onPress={restore} disabled={isPending}>
<Text>Восстановить покупки</Text>
</Button>
);
}

Проверка лимитов

// constants.ts
export const COURSE_LIMITS = {
FREE: 1,
LEARNER: 5,
PRO: Infinity,
TEAM: Infinity,
};

export const CHAT_LIMITS = {
FREE: 5,
LEARNER: 50,
PRO: Infinity,
TEAM: Infinity,
};
// Использование
function useCanCreateCourse() {
const { tier } = useSubscription();
const { data: courses } = useCourses();

const limit = COURSE_LIMITS[tier];
const current = courses?.length || 0;

return current < limit;
}

Экраны

ЭкранПутьОписание
Paywall(app)/subscription/indexВыбор плана
Success(app)/subscription/successПосле покупки
Manage(app)/settings/subscriptionУправление подпиской

Entitlements

RevenueCat entitlements:

- learner    → LEARNER tier
- pro → PRO tier
- team → TEAM tier