feat(vip): 实现VIP服务权限控制和食物识别功能限制

- 添加VIP服务权限检查hook,支持免费使用次数限制
- 为食物识别功能添加登录验证和VIP权限检查
- 优化RevenueCat用户标识同步逻辑
- 修复会员购买状态检查的类型安全问题
- 为营养成分分析添加登录验证
This commit is contained in:
richarjiang
2025-10-29 09:44:30 +08:00
parent eaa7f7275c
commit fcf1be211f
5 changed files with 196 additions and 15 deletions

View File

@@ -72,6 +72,23 @@ const DEFAULT_PLANS: MembershipPlan[] = [
},
];
// RevenueCat 在 JS SDK 中以 string[] 返回 activeSubscriptions但保持健壮性以防类型变化
const getActiveSubscriptionIds = (customerInfo: CustomerInfo): string[] => {
const activeSubscriptions = customerInfo.activeSubscriptions as unknown;
if (Array.isArray(activeSubscriptions)) {
return activeSubscriptions;
}
if (activeSubscriptions && typeof activeSubscriptions === 'object') {
return Object.values(activeSubscriptions as Record<string, string>).filter(
(value): value is string => typeof value === 'string'
);
}
return [];
};
// 权限类型枚举
type PermissionType = 'exclusive' | 'limited' | 'unlimited';
@@ -356,10 +373,11 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members
const listener = (customerInfo: CustomerInfo) => {
log.info('购买状态变化监听器触发', { customerInfo, visible });
const activeSubscriptionIds = getActiveSubscriptionIds(customerInfo);
// 检查是否有有效的购买记录
const hasActiveEntitlements = Object.keys(customerInfo.entitlements.active).length > 0;
const hasNonSubscriptionTransactions = customerInfo.nonSubscriptionTransactions.length > 0;
const hasActiveSubscriptions = Object.keys(customerInfo.activeSubscriptions).length > 0;
const hasActiveSubscriptions = activeSubscriptionIds.length > 0;
if (hasActiveEntitlements || hasNonSubscriptionTransactions || hasActiveSubscriptions) {
capturePurchaseEvent('success', '监听到购买状态变化', {
@@ -368,7 +386,7 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members
hasActiveSubscriptions,
activeEntitlementsCount: Object.keys(customerInfo.entitlements.active).length,
nonSubscriptionTransactionsCount: customerInfo.nonSubscriptionTransactions.length,
activeSubscriptionsCount: Object.keys(customerInfo.activeSubscriptions).length,
activeSubscriptionsCount: activeSubscriptionIds.length,
modalVisible: visible
});
@@ -424,10 +442,12 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members
log.info('获取用户购买信息', { customerInfo });
// 记录详细的购买状态日志
const activeSubscriptionIds = getActiveSubscriptionIds(customerInfo);
captureMessageWithContext('获取用户购买信息成功', {
activeEntitlementsCount: Object.keys(customerInfo.entitlements.active).length,
nonSubscriptionTransactionsCount: customerInfo.nonSubscriptionTransactions.length,
activeSubscriptionsCount: Object.keys(customerInfo.activeSubscriptions).length,
activeSubscriptionsCount: activeSubscriptionIds.length,
originalAppUserId: customerInfo.originalAppUserId,
firstSeen: customerInfo.firstSeen,
originalPurchaseDate: customerInfo.originalPurchaseDate,
@@ -451,7 +471,7 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members
});
// 检查订阅
Object.keys(customerInfo.activeSubscriptions).forEach(productId => {
activeSubscriptionIds.forEach(productId => {
activePurchasedProductIds.push(productId);
log.debug(`激活的订阅: ${productId}`);
});
@@ -517,7 +537,7 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members
captureMessageWithContext('用户没有激活的购买记录', {
hasEntitlements: Object.keys(customerInfo.entitlements.active).length > 0,
hasNonSubscriptions: customerInfo.nonSubscriptionTransactions.length > 0,
hasActiveSubscriptions: Object.keys(customerInfo.activeSubscriptions).length > 0
hasActiveSubscriptions: activeSubscriptionIds.length > 0
});
log.info('用户没有激活的购买记录,使用默认选择逻辑');
setSelectedProduct(availableProducts[0] ?? null);
@@ -611,13 +631,14 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members
log.info('购买成功', { customerInfo, productIdentifier });
const activeSubscriptionIds = getActiveSubscriptionIds(customerInfo);
// 记录购买成功事件
capturePurchaseEvent('success', `购买成功: ${productIdentifier}`, {
productIdentifier,
hasActiveEntitlements: Object.keys(customerInfo.entitlements.active).length > 0,
activeEntitlementsCount: Object.keys(customerInfo.entitlements.active).length,
nonSubscriptionTransactionsCount: customerInfo.nonSubscriptionTransactions.length,
activeSubscriptionsCount: Object.keys(customerInfo.activeSubscriptions).length
activeSubscriptionsCount: activeSubscriptionIds.length
});
// 购买成功后,监听器会自动处理后续逻辑(刷新用户信息、关闭弹窗等)
@@ -687,10 +708,11 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members
const customerInfo = await Purchases.restorePurchases();
log.info('恢复购买结果', { customerInfo });
const activeSubscriptionIds = getActiveSubscriptionIds(customerInfo);
captureMessageWithContext('恢复购买结果', {
activeEntitlementsCount: Object.keys(customerInfo.entitlements.active).length,
nonSubscriptionTransactionsCount: customerInfo.nonSubscriptionTransactions.length,
activeSubscriptionsCount: Object.keys(customerInfo.activeSubscriptions).length,
activeSubscriptionsCount: activeSubscriptionIds.length,
managementUrl: customerInfo.managementURL,
originalAppUserId: customerInfo.originalAppUserId
});
@@ -698,7 +720,7 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members
// 检查是否有有效的购买记录
const hasActiveEntitlements = Object.keys(customerInfo.entitlements.active).length > 0;
const hasNonSubscriptionTransactions = customerInfo.nonSubscriptionTransactions.length > 0;
const hasActiveSubscriptions = Object.keys(customerInfo.activeSubscriptions).length > 0;
const hasActiveSubscriptions = activeSubscriptionIds.length > 0;
if (hasActiveEntitlements || hasNonSubscriptionTransactions || hasActiveSubscriptions) {
// 检查具体的购买内容
@@ -716,7 +738,7 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members
});
// 检查订阅
Object.keys(customerInfo.activeSubscriptions).forEach(productId => {
activeSubscriptionIds.forEach(productId => {
restoredProducts.push(productId);
});
@@ -787,7 +809,7 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members
hasActiveSubscriptions,
activeEntitlementsCount: Object.keys(customerInfo.entitlements.active).length,
nonSubscriptionTransactionsCount: customerInfo.nonSubscriptionTransactions.length,
activeSubscriptionsCount: Object.keys(customerInfo.activeSubscriptions).length
activeSubscriptionsCount: activeSubscriptionIds.length
});
GlobalToast.show({
message: '没有找到购买记录',