feat(membership): 实现会员系统和购买流程

- 创建 MembershipModalContext 统一管理会员弹窗
- 优化 MembershipModal 产品套餐展示和购买流程
- 集成 RevenueCat SDK 并初始化内购功能
- 在个人中心添加会员 Banner,引导非会员用户订阅
- 修复日志工具的循环引用问题,确保错误信息正确记录
- 版本更新至 1.0.20

新增了完整的会员购买流程,包括套餐选择、购买确认、购买恢复等功能。会员 Banner 仅对非会员用户展示,已是会员的用户不会看到。同时优化了错误日志记录,避免循环引用导致的序列化失败。
This commit is contained in:
richarjiang
2025-10-24 09:16:04 +08:00
parent b75a8991ac
commit 2e11f694f8
9 changed files with 437 additions and 186 deletions

View File

@@ -0,0 +1,88 @@
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
import Purchases from 'react-native-purchases';
import { MembershipModal } from '@/components/model/MembershipModal';
import { logger } from '@/utils/logger';
type MembershipModalOptions = {
onPurchaseSuccess?: () => void;
};
interface MembershipModalContextValue {
openMembershipModal: (options?: MembershipModalOptions) => void;
closeMembershipModal: () => void;
}
const MembershipModalContext = createContext<MembershipModalContextValue | null>(null);
export function MembershipModalProvider({ children }: { children: React.ReactNode }) {
const [visible, setVisible] = useState(false);
const [pendingSuccessCallback, setPendingSuccessCallback] = useState<(() => void) | undefined>();
const [isInitialized, setIsInitialized] = useState(false);
useEffect(() => {
// 直接使用生产环境的 API Key避免环境变量问题
const iosApiKey = 'appl_lmVvuLWFlXlrEsnvxMzTnKapqcc';
const initializeRevenueCat = async () => {
try {
// 检查是否已经配置过,避免重复配置
if (!isInitialized) {
await Purchases.configure({ apiKey: iosApiKey });
setIsInitialized(true);
console.log('[MembershipModalProvider] RevenueCat SDK 初始化成功');
}
} catch (error) {
console.error('[MembershipModalProvider] RevenueCat SDK 初始化失败:', error);
// 初始化失败时不阻止应用正常运行
}
};
initializeRevenueCat();
}, [isInitialized]);
const openMembershipModal = useCallback((options?: MembershipModalOptions) => {
setPendingSuccessCallback(() => options?.onPurchaseSuccess);
setVisible(true);
}, []);
const closeMembershipModal = useCallback(() => {
setVisible(false);
setPendingSuccessCallback(undefined);
}, []);
const handlePurchaseSuccess = useCallback(() => {
pendingSuccessCallback?.();
}, [pendingSuccessCallback]);
const contextValue = useMemo(
() => ({
openMembershipModal,
closeMembershipModal,
}),
[closeMembershipModal, openMembershipModal],
);
return (
<MembershipModalContext.Provider value={contextValue}>
{children}
<MembershipModal
visible={visible}
onClose={closeMembershipModal}
onPurchaseSuccess={handlePurchaseSuccess}
/>
</MembershipModalContext.Provider>
);
}
export function useMembershipModal(): MembershipModalContextValue {
const context = useContext(MembershipModalContext);
if (!context) {
logger.error('useMembershipModal must be used within a MembershipModalProvider');
// 抛出错误而不是返回 undefined确保类型安全
throw new Error('useMembershipModal must be used within a MembershipModalProvider');
}
return context;
}