feat(membership): 实现会员系统和购买流程
- 创建 MembershipModalContext 统一管理会员弹窗 - 优化 MembershipModal 产品套餐展示和购买流程 - 集成 RevenueCat SDK 并初始化内购功能 - 在个人中心添加会员 Banner,引导非会员用户订阅 - 修复日志工具的循环引用问题,确保错误信息正确记录 - 版本更新至 1.0.20 新增了完整的会员购买流程,包括套餐选择、购买确认、购买恢复等功能。会员 Banner 仅对非会员用户展示,已是会员的用户不会看到。同时优化了错误日志记录,避免循环引用导致的序列化失败。
This commit is contained in:
@@ -3,6 +3,7 @@ import CustomCheckBox from '@/components/ui/CheckBox';
|
||||
import { USER_AGREEMENT_URL } from '@/constants/Agree';
|
||||
// import { useAuth } from '@/contexts/AuthContext';
|
||||
// import { UserApi } from '@/services';
|
||||
import { log, logger } from '@/utils/logger';
|
||||
import {
|
||||
captureMessage,
|
||||
captureMessageWithContext,
|
||||
@@ -17,10 +18,8 @@ import {
|
||||
ActivityIndicator,
|
||||
Alert,
|
||||
Dimensions,
|
||||
Image,
|
||||
Linking,
|
||||
Modal,
|
||||
Platform,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
Text,
|
||||
@@ -39,70 +38,34 @@ interface MembershipModalProps {
|
||||
|
||||
interface MembershipPlan {
|
||||
id: string;
|
||||
title: string;
|
||||
fallbackTitle: string;
|
||||
subtitle: string;
|
||||
price: string;
|
||||
originalPrice?: string;
|
||||
discount?: string;
|
||||
type: 'weekly' | 'quarterly' | 'lifetime';
|
||||
recommended?: boolean;
|
||||
}
|
||||
|
||||
const DEFAULT_PLANS: MembershipPlan[] = [
|
||||
{
|
||||
id: 'com.ilookai.mind_gpt.Lifetime',
|
||||
title: '终身会员',
|
||||
subtitle: '每天0.01元\n永久有效',
|
||||
price: '¥128',
|
||||
id: 'com.anonymous.digitalpilates.membership.lifetime',
|
||||
fallbackTitle: '终身会员',
|
||||
subtitle: '一次投入,终身健康陪伴',
|
||||
type: 'lifetime',
|
||||
recommended: true,
|
||||
},
|
||||
{
|
||||
id: 'com.ilookai.mind_gpt.ThreeMonths',
|
||||
title: '季度会员',
|
||||
subtitle: '每天1元\n连续包季',
|
||||
price: '¥98',
|
||||
id: 'com.anonymous.digitalpilates.membership.quarter',
|
||||
fallbackTitle: '季度会员',
|
||||
subtitle: '3个月蜕变计划,见证身材变化',
|
||||
type: 'quarterly',
|
||||
},
|
||||
{
|
||||
id: 'weekly_membership',
|
||||
title: '周会员',
|
||||
subtitle: '新人专享\n连续包周',
|
||||
price: '¥18',
|
||||
id: 'com.anonymous.digitalpilates.membership.weekly',
|
||||
fallbackTitle: '周会员',
|
||||
subtitle: '7天体验,开启健康第一步',
|
||||
type: 'weekly',
|
||||
},
|
||||
];
|
||||
|
||||
// {
|
||||
// identifier: 'com.ilookai.mind_gpt.Lifetime',
|
||||
// description: '终身会员',
|
||||
// title: '终身会员',
|
||||
// price: 128,
|
||||
// priceString: '¥128',
|
||||
// pricePerWeek: 128,
|
||||
// pricePerMonth: 128,
|
||||
// pricePerYear: 128,
|
||||
|
||||
// }, {
|
||||
// identifier: 'com.ilookai.mind_gpt.ThreeMonths',
|
||||
// description: '季度会员',
|
||||
// title: '季度会员',
|
||||
// price: 98,
|
||||
// priceString: '¥98',
|
||||
// pricePerWeek: 98,
|
||||
// pricePerMonth: 98,
|
||||
// pricePerYear: 98,
|
||||
// }, {
|
||||
// identifier: 'weekly_membership',
|
||||
// description: '周会员',
|
||||
// title: '周会员',
|
||||
// price: 18,
|
||||
// priceString: '¥18',
|
||||
// pricePerWeek: 18,
|
||||
// pricePerMonth: 18,
|
||||
// pricePerYear: 18,
|
||||
// }
|
||||
|
||||
export function MembershipModal({ visible, onClose, onPurchaseSuccess }: MembershipModalProps) {
|
||||
const [selectedProduct, setSelectedProduct] = useState<PurchasesStoreProduct | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -121,13 +84,18 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members
|
||||
const getTipsContent = (product: PurchasesStoreProduct | null): string => {
|
||||
if (!product) return '';
|
||||
|
||||
// 这里您可以根据不同的产品返回不同的提示内容
|
||||
switch (product.identifier) {
|
||||
case 'com.ilookai.mind_gpt.Lifetime':
|
||||
return '一次购买,永久享受所有功能';
|
||||
case 'com.ilookai.mind_gpt.ThreeMonths':
|
||||
case 'weekly_membership':
|
||||
return '到期后自动续费,可以随时随地取消';
|
||||
const plan = DEFAULT_PLANS.find(item => item.id === product.identifier);
|
||||
if (!plan) {
|
||||
return '';
|
||||
}
|
||||
|
||||
switch (plan.type) {
|
||||
case 'lifetime':
|
||||
return '终身陪伴,见证您的每一次健康蜕变';
|
||||
case 'quarterly':
|
||||
return '3个月科学计划,让健康成为生活习惯';
|
||||
case 'weekly':
|
||||
return '7天体验期,感受专业健康指导的力量';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
@@ -144,88 +112,99 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
setupPurchaseListener();
|
||||
// 重置协议状态
|
||||
setAgreementAccepted(false);
|
||||
initPurchases();
|
||||
} else {
|
||||
// 弹窗关闭时移除监听器
|
||||
removePurchaseListener();
|
||||
setProducts([]);
|
||||
setSelectedProduct(null);
|
||||
setAgreementAccepted(false);
|
||||
setLoading(false);
|
||||
setRestoring(false);
|
||||
}
|
||||
|
||||
// 组件卸载时确保移除监听器
|
||||
return () => {
|
||||
removePurchaseListener();
|
||||
console.log('MembershipModal 购买监听器已清理');
|
||||
log.info('MembershipModal 购买监听器已清理');
|
||||
};
|
||||
}, [visible]); // 依赖 visible 状态
|
||||
|
||||
|
||||
|
||||
const initPurchases = async () => {
|
||||
if (Platform.OS === 'ios') {
|
||||
Purchases.configure({
|
||||
apiKey: 'appl_UXFtPsBsFIsBOxoNGXoPwpXhGYk'
|
||||
});
|
||||
} else if (Platform.OS === 'android') {
|
||||
// Purchases.configure({
|
||||
// apiKey: 'goog_ZqYxWbQvRgLzBnVfYdXcWbVn'
|
||||
// });
|
||||
}
|
||||
|
||||
capturePurchaseEvent('init', 'Purchases 初始化成功');
|
||||
capturePurchaseEvent('init', '开始获取会员产品套餐');
|
||||
|
||||
try {
|
||||
// if (user?.id) {
|
||||
// await Purchases.logIn(user.id);
|
||||
// captureMessageWithContext('用户登录 Purchases 成功', {
|
||||
// userId: user.id
|
||||
// });
|
||||
// }
|
||||
// 添加延迟,确保 RevenueCat SDK 完全初始化
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
const offerings = await Purchases.getOfferings();
|
||||
console.log('offerings', offerings);
|
||||
log.info('获取产品套餐', { offerings });
|
||||
|
||||
captureMessageWithContext('获取产品套餐成功', {
|
||||
logger.info('获取产品套餐成功', {
|
||||
currentOffering: offerings.current?.identifier || null,
|
||||
availablePackagesCount: offerings.current?.availablePackages.length || 0,
|
||||
allOfferingsCount: Object.keys(offerings.all).length
|
||||
allOfferingsCount: Object.keys(offerings.all).length,
|
||||
});
|
||||
|
||||
if (offerings.current !== null && offerings.current.availablePackages.length !== 0) {
|
||||
// 当前活跃的套餐存在
|
||||
const packages = offerings.current.availablePackages;
|
||||
console.log('Available packages:', packages);
|
||||
// packages 是一个数组,包含 PurchasePackage 对象,每个对象都有 product 信息
|
||||
// 可以根据这些信息来渲染你的 UI,例如价格、标题等
|
||||
const sortedProducts = packages.sort((a, b) => b.product.price - a.product.price).map(pkg => pkg.product);
|
||||
setProducts(sortedProducts);
|
||||
const packages = offerings.current?.availablePackages ?? [];
|
||||
|
||||
// 获取产品后,检查用户的购买记录并自动选中对应套餐
|
||||
await checkAndSelectActivePlan(sortedProducts);
|
||||
} else {
|
||||
console.warn('No active offerings found or no packages available.');
|
||||
captureMessageWithContext('没有找到可用的产品套餐', {
|
||||
if (packages.length === 0) {
|
||||
log.warn('没有找到可用的产品套餐', {
|
||||
hasCurrentOffering: offerings.current !== null,
|
||||
packagesLength: offerings.current?.availablePackages.length || 0
|
||||
packagesLength: offerings.current?.availablePackages.length || 0,
|
||||
});
|
||||
logger.info('没有找到可用的产品套餐', {
|
||||
hasCurrentOffering: offerings.current !== null,
|
||||
packagesLength: offerings.current?.availablePackages.length || 0,
|
||||
});
|
||||
setProducts([]);
|
||||
setSelectedProduct(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const matchedProducts = packages
|
||||
.map(pkg => pkg.product)
|
||||
.filter(product => DEFAULT_PLANS.some(plan => plan.id === product.identifier));
|
||||
|
||||
const orderedProducts = DEFAULT_PLANS
|
||||
.map(plan => matchedProducts.find(product => product.identifier === plan.id))
|
||||
.filter((product): product is PurchasesStoreProduct => Boolean(product));
|
||||
|
||||
const fallbackProducts = packages.map(pkg => pkg.product);
|
||||
const productsToUse = orderedProducts.length > 0 ? orderedProducts : fallbackProducts;
|
||||
|
||||
log.info('productsToUse', productsToUse)
|
||||
|
||||
setProducts(productsToUse);
|
||||
|
||||
// 获取产品后,检查用户的购买记录并自动选中对应套餐
|
||||
await checkAndSelectActivePlan(productsToUse);
|
||||
|
||||
setSelectedProduct(current => current ?? (productsToUse[0] ?? null));
|
||||
} catch (e: any) {
|
||||
console.log('error', e);
|
||||
// Error fetching customer info
|
||||
// 安全地处理错误对象,避免循环引用
|
||||
const errorData = {
|
||||
message: e?.message || '未知错误',
|
||||
code: e?.code || null,
|
||||
name: e?.name || 'Error',
|
||||
// 只包含基本的错误信息,避免可能的循环引用
|
||||
};
|
||||
|
||||
log.error('获取产品套餐失败', { error: errorData });
|
||||
captureException(e);
|
||||
// captureMessageWithContext('初始化 Purchases 失败', {
|
||||
// error: e.message || '未知错误',
|
||||
// userId: user?.id || null
|
||||
// });
|
||||
capturePurchaseEvent('error', '获取产品套餐失败', errorData);
|
||||
|
||||
// 设置空状态,避免界面卡在加载状态
|
||||
setProducts([]);
|
||||
setSelectedProduct(null);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('visible', visible);
|
||||
|
||||
};
|
||||
|
||||
// 添加购买状态监听器
|
||||
const setupPurchaseListener = () => {
|
||||
console.log('设置购买监听器,当前 visible 状态:', visible);
|
||||
log.info('设置购买监听器', { visible });
|
||||
|
||||
// 如果已经有监听器,先移除
|
||||
if (purchaseListenerRef.current) {
|
||||
@@ -234,8 +213,7 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members
|
||||
|
||||
// 创建监听器函数
|
||||
const listener = (customerInfo: CustomerInfo) => {
|
||||
console.log('addCustomerInfoUpdateListener:', customerInfo);
|
||||
console.log('addCustomerInfoUpdateListener 触发时 visible 状态:', visible);
|
||||
log.info('购买状态变化监听器触发', { customerInfo, visible });
|
||||
|
||||
// 检查是否有有效的购买记录
|
||||
const hasActiveEntitlements = Object.keys(customerInfo.entitlements.active).length > 0;
|
||||
@@ -253,7 +231,7 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members
|
||||
modalVisible: visible
|
||||
});
|
||||
|
||||
console.log('检测到购买成功,刷新用户信息并关闭弹窗');
|
||||
log.info('检测到购买成功,准备刷新用户信息并关闭弹窗');
|
||||
|
||||
// 延迟一点时间,确保购买流程完全完成
|
||||
setTimeout(async () => {
|
||||
@@ -280,16 +258,16 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members
|
||||
// 添加监听器
|
||||
Purchases.addCustomerInfoUpdateListener(listener);
|
||||
|
||||
console.log('购买监听器已添加');
|
||||
log.info('购买监听器已添加');
|
||||
};
|
||||
|
||||
// 移除购买状态监听器
|
||||
const removePurchaseListener = () => {
|
||||
if (purchaseListenerRef.current) {
|
||||
console.log('移除购买监听器');
|
||||
log.info('移除购买监听器');
|
||||
Purchases.removeCustomerInfoUpdateListener(purchaseListenerRef.current);
|
||||
purchaseListenerRef.current = null;
|
||||
console.log('购买监听器已移除');
|
||||
log.info('购买监听器已移除');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -302,7 +280,7 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members
|
||||
// 获取用户的购买信息
|
||||
const customerInfo = await Purchases.getCustomerInfo();
|
||||
|
||||
console.log('用户购买信息:', customerInfo);
|
||||
log.info('获取用户购买信息', { customerInfo });
|
||||
|
||||
// 记录详细的购买状态日志
|
||||
captureMessageWithContext('获取用户购买信息成功', {
|
||||
@@ -322,19 +300,19 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members
|
||||
Object.keys(customerInfo.entitlements.active).forEach(key => {
|
||||
const entitlement = customerInfo.entitlements.active[key];
|
||||
activePurchasedProductIds.push(entitlement.productIdentifier);
|
||||
console.log(`激活的权益: ${key}, 产品ID: ${entitlement.productIdentifier}`);
|
||||
log.debug(`激活的权益: ${key}, 产品ID: ${entitlement.productIdentifier}`);
|
||||
});
|
||||
|
||||
// 检查非订阅购买(如终身会员)
|
||||
customerInfo.nonSubscriptionTransactions.forEach(transaction => {
|
||||
activePurchasedProductIds.push(transaction.productIdentifier);
|
||||
console.log(`非订阅购买: ${transaction.productIdentifier}, 购买时间: ${transaction.purchaseDate}`);
|
||||
log.debug(`非订阅购买: ${transaction.productIdentifier}, 购买时间: ${transaction.purchaseDate}`);
|
||||
});
|
||||
|
||||
// 检查订阅
|
||||
Object.keys(customerInfo.activeSubscriptions).forEach(productId => {
|
||||
activePurchasedProductIds.push(productId);
|
||||
console.log(`激活的订阅: ${productId}`);
|
||||
log.debug(`激活的订阅: ${productId}`);
|
||||
});
|
||||
|
||||
// 去重
|
||||
@@ -350,18 +328,14 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members
|
||||
let selectedProduct: PurchasesStoreProduct | null = null;
|
||||
|
||||
// 优先级:终身会员 > 季度会员 > 周会员
|
||||
const priorityOrder = [
|
||||
'com.ilookai.mind_gpt.Lifetime',
|
||||
'com.ilookai.mind_gpt.ThreeMonths',
|
||||
'weekly_membership'
|
||||
];
|
||||
const priorityOrder = DEFAULT_PLANS.map(plan => plan.id);
|
||||
|
||||
// 按照优先级查找
|
||||
for (const priorityProductId of priorityOrder) {
|
||||
if (activePurchasedProductIds.includes(priorityProductId)) {
|
||||
selectedProduct = availableProducts.find(product => product.identifier === priorityProductId) || null;
|
||||
if (selectedProduct) {
|
||||
console.log(`找到优先级最高的激活产品: ${priorityProductId}`);
|
||||
log.info(`找到优先级最高的激活产品: ${priorityProductId}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -372,7 +346,7 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members
|
||||
for (const productId of activePurchasedProductIds) {
|
||||
selectedProduct = availableProducts.find(product => product.identifier === productId) || null;
|
||||
if (selectedProduct) {
|
||||
console.log(`找到匹配的激活产品: ${productId}`);
|
||||
log.info(`找到匹配的激活产品: ${productId}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -393,7 +367,10 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members
|
||||
activePurchasedProductIds,
|
||||
availableProductIds: availableProducts.map(p => p.identifier)
|
||||
});
|
||||
console.log('用户有激活的购买记录,但没有找到匹配的可用产品');
|
||||
log.warn('用户有激活的购买记录,但没有找到匹配的可用产品', {
|
||||
activePurchasedProductIds,
|
||||
availableProductIds: availableProducts.map(p => p.identifier)
|
||||
});
|
||||
}
|
||||
} else {
|
||||
captureMessageWithContext('用户没有激活的购买记录', {
|
||||
@@ -401,16 +378,21 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members
|
||||
hasNonSubscriptions: customerInfo.nonSubscriptionTransactions.length > 0,
|
||||
hasActiveSubscriptions: Object.keys(customerInfo.activeSubscriptions).length > 0
|
||||
});
|
||||
console.log('用户没有激活的购买记录,使用默认选择逻辑');
|
||||
log.info('用户没有激活的购买记录,使用默认选择逻辑');
|
||||
setSelectedProduct(availableProducts[0] ?? null);
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
console.log('检查用户购买记录失败:', error);
|
||||
// 安全地处理错误对象,避免循环引用
|
||||
const errorData = {
|
||||
message: error?.message || '未知错误',
|
||||
code: error?.code || null,
|
||||
name: error?.name || 'Error',
|
||||
};
|
||||
|
||||
log.error('检查用户购买记录失败', { error: errorData });
|
||||
captureException(error);
|
||||
captureMessageWithContext('检查用户购买记录失败', {
|
||||
error: error.message || '未知错误',
|
||||
errorCode: error.code || null
|
||||
});
|
||||
captureMessageWithContext('检查用户购买记录失败', errorData);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -467,8 +449,7 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members
|
||||
// 执行购买
|
||||
const { customerInfo, productIdentifier } = await Purchases.purchaseStoreProduct(selectedProduct);
|
||||
|
||||
console.log('购买成功 - customerInfo:', customerInfo);
|
||||
console.log('购买成功 - productIdentifier:', productIdentifier);
|
||||
log.info('购买成功', { customerInfo, productIdentifier });
|
||||
|
||||
// 记录购买成功事件
|
||||
capturePurchaseEvent('success', `购买成功: ${productIdentifier}`, {
|
||||
@@ -480,7 +461,7 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members
|
||||
});
|
||||
|
||||
// 购买成功后,监听器会自动处理后续逻辑(刷新用户信息、关闭弹窗等)
|
||||
console.log('购买流程完成,等待监听器处理后续逻辑');
|
||||
log.info('购买流程完成,等待监听器处理后续逻辑');
|
||||
|
||||
} catch (error: any) {
|
||||
captureException(error);
|
||||
@@ -527,7 +508,7 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members
|
||||
} finally {
|
||||
// 确保在所有情况下都重置加载状态
|
||||
setLoading(false);
|
||||
console.log('购买流程结束,加载状态已重置');
|
||||
log.info('购买流程结束,加载状态已重置');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -545,7 +526,7 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members
|
||||
// 恢复购买
|
||||
const customerInfo = await Purchases.restorePurchases();
|
||||
|
||||
console.log('恢复购买结果:', customerInfo);
|
||||
log.info('恢复购买结果', { customerInfo });
|
||||
captureMessageWithContext('恢复购买结果', {
|
||||
activeEntitlementsCount: Object.keys(customerInfo.entitlements.active).length,
|
||||
nonSubscriptionTransactionsCount: customerInfo.nonSubscriptionTransactions.length,
|
||||
@@ -579,7 +560,7 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members
|
||||
restoredProducts.push(productId);
|
||||
});
|
||||
|
||||
console.log('恢复的产品:', restoredProducts);
|
||||
log.info('恢复的产品', { restoredProducts });
|
||||
capturePurchaseEvent('restore', '恢复购买成功', {
|
||||
restoredProducts,
|
||||
restoredProductsCount: restoredProducts.length
|
||||
@@ -599,7 +580,7 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members
|
||||
// }
|
||||
// });
|
||||
|
||||
// console.log('后台恢复购买响应:', restoreResponse);
|
||||
// log.debug('后台恢复购买响应', { restoreResponse });
|
||||
|
||||
// captureMessageWithContext('后台恢复购买成功', {
|
||||
// responseData: restoreResponse,
|
||||
@@ -620,14 +601,17 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members
|
||||
});
|
||||
|
||||
} catch (apiError: any) {
|
||||
console.log('后台恢复购买接口调用失败:', apiError);
|
||||
|
||||
captureException(apiError);
|
||||
captureMessageWithContext('后台恢复购买接口失败', {
|
||||
error: apiError.message || '未知错误',
|
||||
errorCode: apiError.code || null,
|
||||
// 安全地处理错误对象,避免循环引用
|
||||
const errorData = {
|
||||
message: apiError?.message || '未知错误',
|
||||
code: apiError?.code || null,
|
||||
name: apiError?.name || 'Error',
|
||||
restoredProductsCount: restoredProducts.length
|
||||
});
|
||||
};
|
||||
|
||||
log.error('后台恢复购买接口调用失败', { error: errorData });
|
||||
captureException(apiError);
|
||||
captureMessageWithContext('后台恢复购买接口失败', errorData);
|
||||
|
||||
// 即使后台接口失败,也显示恢复成功(因为 RevenueCat 已经确认有购买记录)
|
||||
// 但不关闭弹窗,让用户知道可能需要重试
|
||||
@@ -650,14 +634,18 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.log('恢复购买失败:', error);
|
||||
// 安全地处理错误对象,避免循环引用
|
||||
const errorData = {
|
||||
message: error?.message || '未知错误',
|
||||
code: error?.code || null,
|
||||
name: error?.name || 'Error',
|
||||
};
|
||||
|
||||
log.error('恢复购买失败', { error: errorData });
|
||||
captureException(error);
|
||||
|
||||
// 记录恢复购买失败事件
|
||||
capturePurchaseEvent('error', `恢复购买失败: ${error.message || '未知错误'}`, {
|
||||
errorCode: error.code || null,
|
||||
errorMessage: error.message || '未知错误'
|
||||
});
|
||||
capturePurchaseEvent('error', `恢复购买失败: ${errorData.message}`, errorData);
|
||||
|
||||
// 处理特定的恢复购买错误
|
||||
if (error.code === 'RESTORE_CANCELLED' || error.code === 'USER_CANCELLED') {
|
||||
@@ -680,39 +668,39 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members
|
||||
} finally {
|
||||
// 确保在所有情况下都重置恢复状态
|
||||
setRestoring(false);
|
||||
console.log('恢复购买流程结束,恢复状态已重置');
|
||||
log.info('恢复购买流程结束,恢复状态已重置');
|
||||
}
|
||||
};
|
||||
|
||||
const renderMembershipBenefits = () => (
|
||||
<View style={styles.benefitsContainer}>
|
||||
<Image source={require('@/assets/images/img_vip_unlock_title.png')} style={styles.benefitsTitleBg} />
|
||||
<View style={styles.benefitItem}>
|
||||
<MaterialIcons name="check-circle" size={20} color="#DF42D0" />
|
||||
<Text style={styles.benefitText}>获得无限制的回复次数</Text>
|
||||
<View style={styles.benefitsTitleContainer}>
|
||||
<Text style={styles.benefitsTitle}>解锁全部健康功能</Text>
|
||||
<Text style={styles.benefitsSubtitle}>开启您的健康蜕变之旅</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.benefitItem}>
|
||||
<MaterialIcons name="check-circle" size={20} color="#DF42D0" />
|
||||
<Text style={styles.benefitText}>获得无限制的个人专属话题库</Text>
|
||||
<Text style={styles.benefitText}>高级营养分析,精准卡路里计算</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.benefitItem}>
|
||||
<MaterialIcons name="check-circle" size={20} color="#DF42D0" />
|
||||
<Text style={styles.benefitText}>获得无广告优质体验</Text>
|
||||
<Text style={styles.benefitText}>定制化减脂计划,科学体重管理</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.benefitItem}>
|
||||
<MaterialIcons name="check-circle" size={20} color="#DF42D0" />
|
||||
<Text style={styles.benefitText}>获得未来免费升级,开启更多功能</Text>
|
||||
<Text style={styles.benefitText}>深度健康数据分析,洞察身体变化</Text>
|
||||
</View>
|
||||
|
||||
|
||||
{/* 皇冠图标 */}
|
||||
<View style={styles.crownContainer}>
|
||||
<Image
|
||||
{/* <Image
|
||||
source={require('@/assets/images/img_profile_vip_bg.png')}
|
||||
style={styles.crownIcon}
|
||||
/>
|
||||
/> */}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
@@ -725,6 +713,8 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members
|
||||
}
|
||||
|
||||
const isSelected = selectedProduct === product;
|
||||
const displayTitle = product.title || plan.fallbackTitle;
|
||||
const priceLabel = product.priceString || '';
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
@@ -738,11 +728,11 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members
|
||||
disabled={loading}
|
||||
activeOpacity={loading ? 1 : 0.8}
|
||||
accessible={true}
|
||||
accessibilityLabel={`${product.title} ${plan.price}`}
|
||||
accessibilityHint={loading ? "购买进行中,无法切换套餐" : `选择${product.title}套餐`}
|
||||
accessibilityLabel={`${displayTitle} ${priceLabel}`}
|
||||
accessibilityHint={loading ? '购买进行中,无法切换套餐' : `选择${displayTitle}套餐`}
|
||||
accessibilityState={{ disabled: loading, selected: isSelected }}
|
||||
>
|
||||
{product.identifier === 'com.ilookai.mind_gpt.Lifetime' && (
|
||||
{plan.recommended && (
|
||||
<View style={styles.recommendedBadge}>
|
||||
<Text style={styles.recommendedText}>推荐</Text>
|
||||
</View>
|
||||
@@ -755,7 +745,7 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members
|
||||
plan.type === 'quarterly' && styles.quarterlyPlanTitle,
|
||||
plan.type === 'weekly' && styles.weeklyPlanTitle,
|
||||
]}>
|
||||
{product.title}
|
||||
{displayTitle}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
@@ -766,7 +756,7 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members
|
||||
plan.type === 'quarterly' && styles.quarterlyPlanPrice,
|
||||
plan.type === 'weekly' && styles.weeklyPlanPrice,
|
||||
]}>
|
||||
{plan.price}
|
||||
{priceLabel || '--'}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
@@ -807,6 +797,13 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members
|
||||
{renderMembershipBenefits()}
|
||||
|
||||
{/* 会员套餐选择 */}
|
||||
{products.length === 0 && (
|
||||
<View style={styles.configurationNotice}>
|
||||
<Text style={styles.configurationText}>
|
||||
暂未获取到会员商品,请在 RevenueCat 中配置 iOS 产品并同步到当前 Offering。
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
<View style={styles.plansContainer}>
|
||||
{products.map(renderPlanCard)}
|
||||
</View>
|
||||
@@ -857,14 +854,20 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.purchaseButton,
|
||||
loading && styles.disabledButton
|
||||
(loading || !selectedProduct) && styles.disabledButton
|
||||
]}
|
||||
onPress={handlePurchase}
|
||||
disabled={loading}
|
||||
disabled={loading || !selectedProduct}
|
||||
accessible={true}
|
||||
accessibilityLabel={loading ? "正在处理购买" : "购买会员"}
|
||||
accessibilityHint={loading ? "购买正在进行中,请稍候" : "点击购买选中的会员套餐"}
|
||||
accessibilityState={{ disabled: loading }}
|
||||
accessibilityLabel={loading ? '正在处理购买' : '购买会员'}
|
||||
accessibilityHint={
|
||||
loading
|
||||
? '购买正在进行中,请稍候'
|
||||
: selectedProduct
|
||||
? `点击购买${selectedProduct.title || '已选'}会员套餐`
|
||||
: '请选择会员套餐后再进行购买'
|
||||
}
|
||||
accessibilityState={{ disabled: loading || !selectedProduct }}
|
||||
>
|
||||
{loading ? (
|
||||
<View style={styles.loadingContainer}>
|
||||
@@ -872,7 +875,7 @@ export function MembershipModal({ visible, onClose, onPurchaseSuccess }: Members
|
||||
<Text style={styles.purchaseButtonText}>正在处理购买...</Text>
|
||||
</View>
|
||||
) : (
|
||||
<Text style={styles.purchaseButtonText}>购买</Text>
|
||||
<Text style={styles.purchaseButtonText}>开启健康蜕变</Text>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
|
||||
@@ -971,6 +974,21 @@ const styles = StyleSheet.create({
|
||||
marginBottom: 30,
|
||||
padding: 20,
|
||||
},
|
||||
benefitsTitleContainer: {
|
||||
alignItems: 'center',
|
||||
marginBottom: 20,
|
||||
},
|
||||
benefitsTitle: {
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold',
|
||||
color: '#333',
|
||||
marginBottom: 5,
|
||||
},
|
||||
benefitsSubtitle: {
|
||||
fontSize: 14,
|
||||
color: '#666',
|
||||
textAlign: 'center',
|
||||
},
|
||||
benefitsTitleBg: {
|
||||
width: 180,
|
||||
height: 20,
|
||||
@@ -1173,4 +1191,4 @@ const styles = StyleSheet.create({
|
||||
lineHeight: 18,
|
||||
textAlign: 'center',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user