- 在 package.json 和 package-lock.json 中新增 @sentry/react-native、react-native-device-info 和 react-native-purchases 依赖 - 更新统计页面,替换 CircularRing 组件为 FitnessRingsCard,增强健身数据展示 - 在布局文件中引入 ToastProvider,优化用户通知体验 - 新增 SuccessToast 组件,提供全局成功提示功能 - 更新健康数据获取逻辑,支持健身圆环数据的提取 - 优化多个组件的样式和交互,提升用户体验
1176 lines
36 KiB
TypeScript
1176 lines
36 KiB
TypeScript
/* eslint-disable react-hooks/exhaustive-deps */
|
||
import CustomCheckBox from '@/components/ui/CheckBox';
|
||
import { USER_AGREEMENT_URL } from '@/constants/Agree';
|
||
// import { useAuth } from '@/contexts/AuthContext';
|
||
// import { UserApi } from '@/services';
|
||
import {
|
||
captureMessage,
|
||
captureMessageWithContext,
|
||
capturePurchaseEvent,
|
||
captureUserAction
|
||
} from '@/utils/sentry.utils';
|
||
import { Toast as GlobalToast } from '@/utils/toast.utils';
|
||
import { MaterialIcons } from '@expo/vector-icons';
|
||
import { captureException } from '@sentry/react-native';
|
||
import React, { useEffect, useRef, useState } from 'react';
|
||
import {
|
||
ActivityIndicator,
|
||
Alert,
|
||
Dimensions,
|
||
Image,
|
||
Linking,
|
||
Modal,
|
||
Platform,
|
||
ScrollView,
|
||
StyleSheet,
|
||
Text,
|
||
TouchableOpacity,
|
||
View
|
||
} from 'react-native';
|
||
import Purchases, { CustomerInfo, PurchasesStoreProduct } from 'react-native-purchases';
|
||
|
||
const { height } = Dimensions.get('window');
|
||
|
||
interface MembershipModalProps {
|
||
visible: boolean;
|
||
onClose?: () => void;
|
||
onPurchaseSuccess?: () => void;
|
||
}
|
||
|
||
interface MembershipPlan {
|
||
id: string;
|
||
title: 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',
|
||
type: 'lifetime',
|
||
recommended: true,
|
||
},
|
||
{
|
||
id: 'com.ilookai.mind_gpt.ThreeMonths',
|
||
title: '季度会员',
|
||
subtitle: '每天1元\n连续包季',
|
||
price: '¥98',
|
||
type: 'quarterly',
|
||
},
|
||
{
|
||
id: 'weekly_membership',
|
||
title: '周会员',
|
||
subtitle: '新人专享\n连续包周',
|
||
price: '¥18',
|
||
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);
|
||
const [restoring, setRestoring] = useState(false);
|
||
// const { user } = useAuth()
|
||
// const { refreshUserInfo } = useAuth();
|
||
const [products, setProducts] = useState<PurchasesStoreProduct[]>([]);
|
||
|
||
// 协议同意状态 - 只需要一个状态
|
||
const [agreementAccepted, setAgreementAccepted] = useState(false);
|
||
|
||
// 保存监听器引用,用于移除监听器
|
||
const purchaseListenerRef = useRef<((customerInfo: CustomerInfo) => void) | null>(null);
|
||
|
||
// 根据选中的产品生成tips内容
|
||
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 '到期后自动续费,可以随时随地取消';
|
||
default:
|
||
return '';
|
||
}
|
||
};
|
||
|
||
// useEffect(() => {
|
||
// if (user) {
|
||
// captureMessage('用户已登录,开始初始化 Purchases');
|
||
// initPurchases();
|
||
// }
|
||
// }, [user]); // 初始化只需要执行一次
|
||
|
||
// 单独的 useEffect 来处理购买监听器,依赖 visible 状态
|
||
useEffect(() => {
|
||
if (visible) {
|
||
setupPurchaseListener();
|
||
// 重置协议状态
|
||
setAgreementAccepted(false);
|
||
} else {
|
||
// 弹窗关闭时移除监听器
|
||
removePurchaseListener();
|
||
}
|
||
|
||
// 组件卸载时确保移除监听器
|
||
return () => {
|
||
removePurchaseListener();
|
||
console.log('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 初始化成功');
|
||
|
||
try {
|
||
// if (user?.id) {
|
||
// await Purchases.logIn(user.id);
|
||
// captureMessageWithContext('用户登录 Purchases 成功', {
|
||
// userId: user.id
|
||
// });
|
||
// }
|
||
|
||
const offerings = await Purchases.getOfferings();
|
||
console.log('offerings', offerings);
|
||
|
||
captureMessageWithContext('获取产品套餐成功', {
|
||
currentOffering: offerings.current?.identifier || null,
|
||
availablePackagesCount: offerings.current?.availablePackages.length || 0,
|
||
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);
|
||
|
||
// 获取产品后,检查用户的购买记录并自动选中对应套餐
|
||
await checkAndSelectActivePlan(sortedProducts);
|
||
} else {
|
||
console.warn('No active offerings found or no packages available.');
|
||
captureMessageWithContext('没有找到可用的产品套餐', {
|
||
hasCurrentOffering: offerings.current !== null,
|
||
packagesLength: offerings.current?.availablePackages.length || 0
|
||
});
|
||
}
|
||
|
||
} catch (e: any) {
|
||
console.log('error', e);
|
||
// Error fetching customer info
|
||
captureException(e);
|
||
// captureMessageWithContext('初始化 Purchases 失败', {
|
||
// error: e.message || '未知错误',
|
||
// userId: user?.id || null
|
||
// });
|
||
}
|
||
}
|
||
|
||
console.log('visible', visible);
|
||
|
||
|
||
// 添加购买状态监听器
|
||
const setupPurchaseListener = () => {
|
||
console.log('设置购买监听器,当前 visible 状态:', visible);
|
||
|
||
// 如果已经有监听器,先移除
|
||
if (purchaseListenerRef.current) {
|
||
removePurchaseListener();
|
||
}
|
||
|
||
// 创建监听器函数
|
||
const listener = (customerInfo: CustomerInfo) => {
|
||
console.log('addCustomerInfoUpdateListener:', customerInfo);
|
||
console.log('addCustomerInfoUpdateListener 触发时 visible 状态:', visible);
|
||
|
||
// 检查是否有有效的购买记录
|
||
const hasActiveEntitlements = Object.keys(customerInfo.entitlements.active).length > 0;
|
||
const hasNonSubscriptionTransactions = customerInfo.nonSubscriptionTransactions.length > 0;
|
||
const hasActiveSubscriptions = Object.keys(customerInfo.activeSubscriptions).length > 0;
|
||
|
||
if (hasActiveEntitlements || hasNonSubscriptionTransactions || hasActiveSubscriptions) {
|
||
capturePurchaseEvent('success', '监听到购买状态变化', {
|
||
hasActiveEntitlements,
|
||
hasNonSubscriptionTransactions,
|
||
hasActiveSubscriptions,
|
||
activeEntitlementsCount: Object.keys(customerInfo.entitlements.active).length,
|
||
nonSubscriptionTransactionsCount: customerInfo.nonSubscriptionTransactions.length,
|
||
activeSubscriptionsCount: Object.keys(customerInfo.activeSubscriptions).length,
|
||
modalVisible: visible
|
||
});
|
||
|
||
console.log('检测到购买成功,刷新用户信息并关闭弹窗');
|
||
|
||
// 延迟一点时间,确保购买流程完全完成
|
||
setTimeout(async () => {
|
||
// 刷新用户信息
|
||
// await refreshUserInfo();
|
||
|
||
// 调用购买成功回调
|
||
onPurchaseSuccess?.();
|
||
|
||
// 关闭弹窗
|
||
onClose?.();
|
||
|
||
// 显示成功提示
|
||
GlobalToast.show({
|
||
message: '会员开通成功',
|
||
});
|
||
}, 1000);
|
||
}
|
||
};
|
||
|
||
// 保存监听器引用
|
||
purchaseListenerRef.current = listener;
|
||
|
||
// 添加监听器
|
||
Purchases.addCustomerInfoUpdateListener(listener);
|
||
|
||
console.log('购买监听器已添加');
|
||
};
|
||
|
||
// 移除购买状态监听器
|
||
const removePurchaseListener = () => {
|
||
if (purchaseListenerRef.current) {
|
||
console.log('移除购买监听器');
|
||
Purchases.removeCustomerInfoUpdateListener(purchaseListenerRef.current);
|
||
purchaseListenerRef.current = null;
|
||
console.log('购买监听器已移除');
|
||
}
|
||
};
|
||
|
||
|
||
// 检查用户的购买记录并自动选中对应套餐
|
||
const checkAndSelectActivePlan = async (availableProducts: PurchasesStoreProduct[]) => {
|
||
try {
|
||
captureUserAction('开始检查用户购买记录');
|
||
|
||
// 获取用户的购买信息
|
||
const customerInfo = await Purchases.getCustomerInfo();
|
||
|
||
console.log('用户购买信息:', customerInfo);
|
||
|
||
// 记录详细的购买状态日志
|
||
captureMessageWithContext('获取用户购买信息成功', {
|
||
activeEntitlementsCount: Object.keys(customerInfo.entitlements.active).length,
|
||
nonSubscriptionTransactionsCount: customerInfo.nonSubscriptionTransactions.length,
|
||
activeSubscriptionsCount: Object.keys(customerInfo.activeSubscriptions).length,
|
||
originalAppUserId: customerInfo.originalAppUserId,
|
||
firstSeen: customerInfo.firstSeen,
|
||
originalPurchaseDate: customerInfo.originalPurchaseDate,
|
||
latestExpirationDate: customerInfo.latestExpirationDate
|
||
});
|
||
|
||
// 查找激活的产品ID
|
||
let activePurchasedProductIds: string[] = [];
|
||
|
||
// 检查权益
|
||
Object.keys(customerInfo.entitlements.active).forEach(key => {
|
||
const entitlement = customerInfo.entitlements.active[key];
|
||
activePurchasedProductIds.push(entitlement.productIdentifier);
|
||
console.log(`激活的权益: ${key}, 产品ID: ${entitlement.productIdentifier}`);
|
||
});
|
||
|
||
// 检查非订阅购买(如终身会员)
|
||
customerInfo.nonSubscriptionTransactions.forEach(transaction => {
|
||
activePurchasedProductIds.push(transaction.productIdentifier);
|
||
console.log(`非订阅购买: ${transaction.productIdentifier}, 购买时间: ${transaction.purchaseDate}`);
|
||
});
|
||
|
||
// 检查订阅
|
||
Object.keys(customerInfo.activeSubscriptions).forEach(productId => {
|
||
activePurchasedProductIds.push(productId);
|
||
console.log(`激活的订阅: ${productId}`);
|
||
});
|
||
|
||
// 去重
|
||
activePurchasedProductIds = [...new Set(activePurchasedProductIds)];
|
||
|
||
captureMessageWithContext('用户激活的产品列表', {
|
||
activePurchasedProductIds,
|
||
activePurchasedProductCount: activePurchasedProductIds.length
|
||
});
|
||
|
||
if (activePurchasedProductIds.length > 0) {
|
||
// 尝试在可用产品中找到匹配的产品
|
||
let selectedProduct: PurchasesStoreProduct | null = null;
|
||
|
||
// 优先级:终身会员 > 季度会员 > 周会员
|
||
const priorityOrder = [
|
||
'com.ilookai.mind_gpt.Lifetime',
|
||
'com.ilookai.mind_gpt.ThreeMonths',
|
||
'weekly_membership'
|
||
];
|
||
|
||
// 按照优先级查找
|
||
for (const priorityProductId of priorityOrder) {
|
||
if (activePurchasedProductIds.includes(priorityProductId)) {
|
||
selectedProduct = availableProducts.find(product => product.identifier === priorityProductId) || null;
|
||
if (selectedProduct) {
|
||
console.log(`找到优先级最高的激活产品: ${priorityProductId}`);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 如果按优先级没找到,尝试找到任何匹配的产品
|
||
if (!selectedProduct) {
|
||
for (const productId of activePurchasedProductIds) {
|
||
selectedProduct = availableProducts.find(product => product.identifier === productId) || null;
|
||
if (selectedProduct) {
|
||
console.log(`找到匹配的激活产品: ${productId}`);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (selectedProduct) {
|
||
setSelectedProduct(selectedProduct);
|
||
|
||
captureMessageWithContext('自动选中用户已购买的套餐', {
|
||
selectedProductId: selectedProduct.identifier,
|
||
selectedProductTitle: selectedProduct.title,
|
||
selectedProductPrice: selectedProduct.price,
|
||
allActivePurchasedProductIds: activePurchasedProductIds
|
||
});
|
||
|
||
} else {
|
||
captureMessageWithContext('未找到匹配的可用产品', {
|
||
activePurchasedProductIds,
|
||
availableProductIds: availableProducts.map(p => p.identifier)
|
||
});
|
||
console.log('用户有激活的购买记录,但没有找到匹配的可用产品');
|
||
}
|
||
} else {
|
||
captureMessageWithContext('用户没有激活的购买记录', {
|
||
hasEntitlements: Object.keys(customerInfo.entitlements.active).length > 0,
|
||
hasNonSubscriptions: customerInfo.nonSubscriptionTransactions.length > 0,
|
||
hasActiveSubscriptions: Object.keys(customerInfo.activeSubscriptions).length > 0
|
||
});
|
||
console.log('用户没有激活的购买记录,使用默认选择逻辑');
|
||
}
|
||
|
||
} catch (error: any) {
|
||
console.log('检查用户购买记录失败:', error);
|
||
captureException(error);
|
||
captureMessageWithContext('检查用户购买记录失败', {
|
||
error: error.message || '未知错误',
|
||
errorCode: error.code || null
|
||
});
|
||
}
|
||
};
|
||
|
||
|
||
const handlePurchase = async () => {
|
||
// 验证是否已同意协议
|
||
if (!agreementAccepted) {
|
||
Alert.alert(
|
||
'请阅读并同意相关协议',
|
||
'购买前需要同意用户协议、会员协议和自动续费协议',
|
||
[
|
||
{
|
||
text: '确定',
|
||
style: 'default',
|
||
}
|
||
]
|
||
);
|
||
return;
|
||
}
|
||
|
||
// 验证是否选择了产品
|
||
if (!selectedProduct) {
|
||
Alert.alert(
|
||
'请选择会员套餐',
|
||
'',
|
||
[
|
||
{
|
||
text: '确定',
|
||
style: 'default',
|
||
}
|
||
]
|
||
);
|
||
return;
|
||
}
|
||
|
||
|
||
|
||
// 防止重复点击
|
||
if (loading) {
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// 设置加载状态
|
||
setLoading(true);
|
||
|
||
// 记录购买开始事件
|
||
capturePurchaseEvent('init', `开始购买: ${selectedProduct.identifier}`, {
|
||
productIdentifier: selectedProduct.identifier,
|
||
productTitle: selectedProduct.title,
|
||
productPrice: selectedProduct.price
|
||
});
|
||
|
||
// 执行购买
|
||
const { customerInfo, productIdentifier } = await Purchases.purchaseStoreProduct(selectedProduct);
|
||
|
||
console.log('购买成功 - customerInfo:', customerInfo);
|
||
console.log('购买成功 - productIdentifier:', productIdentifier);
|
||
|
||
// 记录购买成功事件
|
||
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
|
||
});
|
||
|
||
// 购买成功后,监听器会自动处理后续逻辑(刷新用户信息、关闭弹窗等)
|
||
console.log('购买流程完成,等待监听器处理后续逻辑');
|
||
|
||
} catch (error: any) {
|
||
captureException(error);
|
||
|
||
// 记录购买失败事件
|
||
capturePurchaseEvent('error', `购买失败: ${error.message || '未知错误'}`, {
|
||
errorCode: error.code || null,
|
||
errorMessage: error.message || '未知错误',
|
||
productIdentifier: selectedProduct.identifier
|
||
});
|
||
|
||
// 处理不同类型的购买错误
|
||
if (error.code === 1 || error.code === 'USER_CANCELLED') {
|
||
// 用户取消购买
|
||
GlobalToast.show({
|
||
message: '购买已取消',
|
||
});
|
||
} else if (error.code === 'ITEM_ALREADY_OWNED' || error.code === 'PRODUCT_ALREADY_PURCHASED') {
|
||
// 商品已拥有
|
||
GlobalToast.show({
|
||
message: '您已拥有此商品',
|
||
});
|
||
} else if (error.code === 'NETWORK_ERROR') {
|
||
// 网络错误
|
||
GlobalToast.show({
|
||
message: '网络连接失败',
|
||
});
|
||
} else if (error.code === 'PAYMENT_PENDING') {
|
||
// 支付待处理
|
||
GlobalToast.show({
|
||
message: '支付正在处理中',
|
||
});
|
||
} else if (error.code === 'INVALID_CREDENTIALS') {
|
||
// 凭据无效
|
||
GlobalToast.show({
|
||
message: '账户验证失败',
|
||
});
|
||
} else {
|
||
// 其他错误
|
||
GlobalToast.show({
|
||
message: '购买失败',
|
||
});
|
||
}
|
||
} finally {
|
||
// 确保在所有情况下都重置加载状态
|
||
setLoading(false);
|
||
console.log('购买流程结束,加载状态已重置');
|
||
}
|
||
};
|
||
|
||
const handleRestore = async () => {
|
||
// 防止重复点击
|
||
if (restoring || loading) {
|
||
return;
|
||
}
|
||
|
||
try {
|
||
setRestoring(true);
|
||
|
||
captureUserAction('开始恢复购买');
|
||
|
||
// 恢复购买
|
||
const customerInfo = await Purchases.restorePurchases();
|
||
|
||
console.log('恢复购买结果:', customerInfo);
|
||
captureMessageWithContext('恢复购买结果', {
|
||
activeEntitlementsCount: Object.keys(customerInfo.entitlements.active).length,
|
||
nonSubscriptionTransactionsCount: customerInfo.nonSubscriptionTransactions.length,
|
||
activeSubscriptionsCount: Object.keys(customerInfo.activeSubscriptions).length,
|
||
managementUrl: customerInfo.managementURL,
|
||
originalAppUserId: customerInfo.originalAppUserId
|
||
});
|
||
|
||
// 检查是否有有效的购买记录
|
||
const hasActiveEntitlements = Object.keys(customerInfo.entitlements.active).length > 0;
|
||
const hasNonSubscriptionTransactions = customerInfo.nonSubscriptionTransactions.length > 0;
|
||
const hasActiveSubscriptions = Object.keys(customerInfo.activeSubscriptions).length > 0;
|
||
|
||
if (hasActiveEntitlements || hasNonSubscriptionTransactions || hasActiveSubscriptions) {
|
||
// 检查具体的购买内容
|
||
let restoredProducts: string[] = [];
|
||
|
||
// 检查权益
|
||
Object.keys(customerInfo.entitlements.active).forEach(key => {
|
||
const entitlement = customerInfo.entitlements.active[key];
|
||
restoredProducts.push(entitlement.productIdentifier);
|
||
});
|
||
|
||
// 检查非订阅购买(如终身会员)
|
||
customerInfo.nonSubscriptionTransactions.forEach(transaction => {
|
||
restoredProducts.push(transaction.productIdentifier);
|
||
});
|
||
|
||
// 检查订阅
|
||
Object.keys(customerInfo.activeSubscriptions).forEach(productId => {
|
||
restoredProducts.push(productId);
|
||
});
|
||
|
||
console.log('恢复的产品:', restoredProducts);
|
||
capturePurchaseEvent('restore', '恢复购买成功', {
|
||
restoredProducts,
|
||
restoredProductsCount: restoredProducts.length
|
||
});
|
||
|
||
try {
|
||
// 调用后台服务接口进行票据匹配
|
||
captureUserAction('开始调用后台恢复购买接口');
|
||
|
||
// const restoreResponse = await UserApi.restorePurchase({
|
||
// customerInfo: {
|
||
// originalAppUserId: customerInfo.originalAppUserId,
|
||
// activeEntitlements: customerInfo.entitlements.active,
|
||
// nonSubscriptionTransactions: customerInfo.nonSubscriptionTransactions,
|
||
// activeSubscriptions: customerInfo.activeSubscriptions,
|
||
// restoredProducts
|
||
// }
|
||
// });
|
||
|
||
// console.log('后台恢复购买响应:', restoreResponse);
|
||
|
||
// captureMessageWithContext('后台恢复购买成功', {
|
||
// responseData: restoreResponse,
|
||
// restoredProductsCount: restoredProducts.length
|
||
// });
|
||
|
||
// 刷新用户信息
|
||
// await refreshUserInfo();
|
||
|
||
// 调用购买成功回调
|
||
onPurchaseSuccess?.();
|
||
|
||
// 关闭弹窗
|
||
onClose?.();
|
||
|
||
GlobalToast.show({
|
||
message: '恢复购买成功',
|
||
});
|
||
|
||
} catch (apiError: any) {
|
||
console.log('后台恢复购买接口调用失败:', apiError);
|
||
|
||
captureException(apiError);
|
||
captureMessageWithContext('后台恢复购买接口失败', {
|
||
error: apiError.message || '未知错误',
|
||
errorCode: apiError.code || null,
|
||
restoredProductsCount: restoredProducts.length
|
||
});
|
||
|
||
// 即使后台接口失败,也显示恢复成功(因为 RevenueCat 已经确认有购买记录)
|
||
// 但不关闭弹窗,让用户知道可能需要重试
|
||
GlobalToast.show({
|
||
message: '恢复购买部分失败',
|
||
});
|
||
}
|
||
|
||
} else {
|
||
capturePurchaseEvent('restore', '没有找到购买记录', {
|
||
hasActiveEntitlements,
|
||
hasNonSubscriptionTransactions,
|
||
hasActiveSubscriptions,
|
||
activeEntitlementsCount: Object.keys(customerInfo.entitlements.active).length,
|
||
nonSubscriptionTransactionsCount: customerInfo.nonSubscriptionTransactions.length,
|
||
activeSubscriptionsCount: Object.keys(customerInfo.activeSubscriptions).length
|
||
});
|
||
GlobalToast.show({
|
||
message: '没有找到购买记录',
|
||
});
|
||
}
|
||
} catch (error: any) {
|
||
console.log('恢复购买失败:', error);
|
||
captureException(error);
|
||
|
||
// 记录恢复购买失败事件
|
||
capturePurchaseEvent('error', `恢复购买失败: ${error.message || '未知错误'}`, {
|
||
errorCode: error.code || null,
|
||
errorMessage: error.message || '未知错误'
|
||
});
|
||
|
||
// 处理特定的恢复购买错误
|
||
if (error.code === 'RESTORE_CANCELLED' || error.code === 'USER_CANCELLED') {
|
||
GlobalToast.show({
|
||
message: '恢复购买已取消',
|
||
});
|
||
} else if (error.code === 'NETWORK_ERROR') {
|
||
GlobalToast.show({
|
||
message: '网络错误',
|
||
});
|
||
} else if (error.code === 'INVALID_CREDENTIALS') {
|
||
GlobalToast.show({
|
||
message: '账户验证失败',
|
||
});
|
||
} else {
|
||
GlobalToast.show({
|
||
message: '恢复购买失败',
|
||
});
|
||
}
|
||
} finally {
|
||
// 确保在所有情况下都重置恢复状态
|
||
setRestoring(false);
|
||
console.log('恢复购买流程结束,恢复状态已重置');
|
||
}
|
||
};
|
||
|
||
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>
|
||
|
||
<View style={styles.benefitItem}>
|
||
<MaterialIcons name="check-circle" size={20} color="#DF42D0" />
|
||
<Text style={styles.benefitText}>获得无限制的个人专属话题库</Text>
|
||
</View>
|
||
|
||
<View style={styles.benefitItem}>
|
||
<MaterialIcons name="check-circle" size={20} color="#DF42D0" />
|
||
<Text style={styles.benefitText}>获得无广告优质体验</Text>
|
||
</View>
|
||
|
||
<View style={styles.benefitItem}>
|
||
<MaterialIcons name="check-circle" size={20} color="#DF42D0" />
|
||
<Text style={styles.benefitText}>获得未来免费升级,开启更多功能</Text>
|
||
</View>
|
||
|
||
{/* 皇冠图标 */}
|
||
<View style={styles.crownContainer}>
|
||
<Image
|
||
source={require('@/assets/images/img_profile_vip_bg.png')}
|
||
style={styles.crownIcon}
|
||
/>
|
||
</View>
|
||
</View>
|
||
);
|
||
|
||
const renderPlanCard = (product: PurchasesStoreProduct) => {
|
||
const plan = DEFAULT_PLANS.find(p => p.id === product.identifier);
|
||
|
||
if (!plan) {
|
||
return null;
|
||
}
|
||
|
||
const isSelected = selectedProduct === product;
|
||
|
||
return (
|
||
<TouchableOpacity
|
||
key={product.identifier}
|
||
style={[
|
||
styles.planCard,
|
||
isSelected && styles.selectedPlan,
|
||
loading && styles.disabledPlanCard,
|
||
]}
|
||
onPress={() => !loading && product && setSelectedProduct(product)}
|
||
disabled={loading}
|
||
activeOpacity={loading ? 1 : 0.8}
|
||
accessible={true}
|
||
accessibilityLabel={`${product.title} ${plan.price}`}
|
||
accessibilityHint={loading ? "购买进行中,无法切换套餐" : `选择${product.title}套餐`}
|
||
accessibilityState={{ disabled: loading, selected: isSelected }}
|
||
>
|
||
{product.identifier === 'com.ilookai.mind_gpt.Lifetime' && (
|
||
<View style={styles.recommendedBadge}>
|
||
<Text style={styles.recommendedText}>推荐</Text>
|
||
</View>
|
||
)}
|
||
|
||
<View style={styles.planHeader}>
|
||
<Text style={[
|
||
styles.planTitle,
|
||
plan.type === 'lifetime' && styles.lifetimePlanTitle,
|
||
plan.type === 'quarterly' && styles.quarterlyPlanTitle,
|
||
plan.type === 'weekly' && styles.weeklyPlanTitle,
|
||
]}>
|
||
{product.title}
|
||
</Text>
|
||
</View>
|
||
|
||
<View style={styles.planPricing}>
|
||
<Text style={[
|
||
styles.planPrice,
|
||
plan.type === 'lifetime' && styles.lifetimePlanPrice,
|
||
plan.type === 'quarterly' && styles.quarterlyPlanPrice,
|
||
plan.type === 'weekly' && styles.weeklyPlanPrice,
|
||
]}>
|
||
{plan.price}
|
||
</Text>
|
||
</View>
|
||
|
||
<Text style={styles.planSubtitle}>{plan.subtitle}</Text>
|
||
</TouchableOpacity>
|
||
);
|
||
};
|
||
|
||
|
||
return (
|
||
<Modal
|
||
visible={visible}
|
||
transparent={true}
|
||
animationType="slide"
|
||
presentationStyle="overFullScreen"
|
||
>
|
||
<View style={styles.overlay}>
|
||
{/* 半透明背景 */}
|
||
<TouchableOpacity
|
||
style={styles.backdrop}
|
||
activeOpacity={1}
|
||
onPress={onClose} // 阻止点击背景关闭
|
||
/>
|
||
|
||
{/* 会员内容 */}
|
||
<View style={styles.modalContainer}>
|
||
<ScrollView style={styles.modalContent} showsVerticalScrollIndicator={false}>
|
||
{/* 关闭按钮 */}
|
||
{/* <TouchableOpacity
|
||
style={styles.closeButton}
|
||
onPress={onClose}
|
||
>
|
||
<Text style={styles.closeButtonText}>×</Text>
|
||
</TouchableOpacity> */}
|
||
|
||
|
||
{/* 会员权益介绍 */}
|
||
{renderMembershipBenefits()}
|
||
|
||
{/* 会员套餐选择 */}
|
||
<View style={styles.plansContainer}>
|
||
{products.map(renderPlanCard)}
|
||
</View>
|
||
|
||
{/* 产品选择提示区域 */}
|
||
{selectedProduct && (
|
||
<View style={styles.tipsContainer}>
|
||
<Text style={styles.tipsText}>
|
||
{getTipsContent(selectedProduct)}
|
||
</Text>
|
||
</View>
|
||
)}
|
||
|
||
{/* 协议同意区域 */}
|
||
<View style={styles.agreementRow}>
|
||
<CustomCheckBox
|
||
checked={agreementAccepted}
|
||
onCheckedChange={setAgreementAccepted}
|
||
size={16}
|
||
checkedColor="#E91E63"
|
||
uncheckedColor="#999"
|
||
/>
|
||
<Text style={styles.agreementPrefix}>我已阅读并同意</Text>
|
||
<TouchableOpacity onPress={() => {
|
||
Linking.openURL(USER_AGREEMENT_URL);
|
||
captureMessage('click user agreement');
|
||
}}>
|
||
<Text style={styles.agreementLink}>《用户协议》</Text>
|
||
</TouchableOpacity>
|
||
<Text style={styles.agreementSeparator}> | </Text>
|
||
<TouchableOpacity onPress={() => {
|
||
// Linking.openURL(MEMBERSHIP_AGREEMENT_URL);
|
||
captureMessage('click membership agreement');
|
||
}}>
|
||
<Text style={styles.agreementLink}>《会员协议》</Text>
|
||
</TouchableOpacity>
|
||
<Text style={styles.agreementSeparator}> | </Text>
|
||
<TouchableOpacity onPress={() => {
|
||
// Linking.openURL(AUTO_RENEWAL_AGREEMENT_URL);
|
||
captureMessage('click auto renewal agreement');
|
||
}}>
|
||
<Text style={styles.agreementLink}>《自动续费协议》</Text>
|
||
</TouchableOpacity>
|
||
</View>
|
||
|
||
|
||
{/* 购买按钮 */}
|
||
<TouchableOpacity
|
||
style={[
|
||
styles.purchaseButton,
|
||
loading && styles.disabledButton
|
||
]}
|
||
onPress={handlePurchase}
|
||
disabled={loading}
|
||
accessible={true}
|
||
accessibilityLabel={loading ? "正在处理购买" : "购买会员"}
|
||
accessibilityHint={loading ? "购买正在进行中,请稍候" : "点击购买选中的会员套餐"}
|
||
accessibilityState={{ disabled: loading }}
|
||
>
|
||
{loading ? (
|
||
<View style={styles.loadingContainer}>
|
||
<ActivityIndicator size="small" color="white" style={styles.loadingSpinner} />
|
||
<Text style={styles.purchaseButtonText}>正在处理购买...</Text>
|
||
</View>
|
||
) : (
|
||
<Text style={styles.purchaseButtonText}>购买</Text>
|
||
)}
|
||
</TouchableOpacity>
|
||
|
||
{/* 恢复购买按钮 */}
|
||
<TouchableOpacity
|
||
style={[styles.restoreButton, (restoring || loading) && styles.disabledRestoreButton]}
|
||
onPress={handleRestore}
|
||
disabled={restoring || loading}
|
||
>
|
||
{restoring ? (
|
||
<View style={styles.restoreButtonContent}>
|
||
<ActivityIndicator size="small" color="#666" style={styles.restoreButtonLoader} />
|
||
<Text style={styles.restoreButtonText}>恢复中...</Text>
|
||
</View>
|
||
) : (
|
||
<Text style={styles.restoreButtonText}>恢复购买</Text>
|
||
)}
|
||
</TouchableOpacity>
|
||
</ScrollView>
|
||
</View>
|
||
</View>
|
||
</Modal>
|
||
);
|
||
}
|
||
|
||
const styles = StyleSheet.create({
|
||
overlay: {
|
||
flex: 1,
|
||
justifyContent: 'flex-end',
|
||
},
|
||
backdrop: {
|
||
...StyleSheet.absoluteFillObject,
|
||
backgroundColor: 'rgba(0, 0, 0, 0.3)',
|
||
},
|
||
modalContainer: {
|
||
height: height * 0.6,
|
||
backgroundColor: 'white',
|
||
borderTopLeftRadius: 20,
|
||
borderTopRightRadius: 20,
|
||
overflow: 'hidden',
|
||
},
|
||
modalContent: {
|
||
flex: 1,
|
||
paddingHorizontal: 20,
|
||
paddingTop: 20,
|
||
},
|
||
closeButton: {
|
||
position: 'absolute',
|
||
top: 0,
|
||
right: 0,
|
||
width: 30,
|
||
height: 30,
|
||
borderRadius: 15,
|
||
backgroundColor: 'rgba(0, 0, 0, 0.1)',
|
||
justifyContent: 'center',
|
||
alignItems: 'center',
|
||
zIndex: 1,
|
||
},
|
||
closeButtonText: {
|
||
fontSize: 18,
|
||
color: '#666',
|
||
fontWeight: '300',
|
||
},
|
||
configurationNotice: {
|
||
backgroundColor: '#FFF9E6',
|
||
borderRadius: 12,
|
||
padding: 16,
|
||
marginTop: 30,
|
||
marginBottom: 20,
|
||
alignItems: 'center',
|
||
borderWidth: 1,
|
||
borderColor: '#FFE4B5',
|
||
},
|
||
configurationText: {
|
||
fontSize: 14,
|
||
color: '#B8860B',
|
||
textAlign: 'center',
|
||
marginTop: 8,
|
||
marginBottom: 12,
|
||
},
|
||
configurationButton: {
|
||
backgroundColor: '#FF9500',
|
||
borderRadius: 8,
|
||
paddingHorizontal: 16,
|
||
paddingVertical: 8,
|
||
},
|
||
configurationButtonText: {
|
||
color: 'white',
|
||
fontSize: 12,
|
||
fontWeight: '600',
|
||
},
|
||
benefitsContainer: {
|
||
position: 'relative',
|
||
backgroundColor: '#FFE7F9',
|
||
borderRadius: 16,
|
||
marginBottom: 30,
|
||
padding: 20,
|
||
},
|
||
benefitsTitleBg: {
|
||
width: 180,
|
||
height: 20,
|
||
marginBottom: 10,
|
||
},
|
||
benefitItem: {
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
marginTop: 10,
|
||
},
|
||
benefitText: {
|
||
fontSize: 14,
|
||
color: '#333',
|
||
marginLeft: 8,
|
||
fontWeight: '500',
|
||
},
|
||
crownContainer: {
|
||
position: 'absolute',
|
||
top: 10,
|
||
right: 10,
|
||
width: 100,
|
||
height: 100,
|
||
justifyContent: 'center',
|
||
alignItems: 'center',
|
||
},
|
||
crownIcon: {
|
||
width: 80,
|
||
height: 80,
|
||
resizeMode: 'contain',
|
||
},
|
||
plansContainer: {
|
||
flexDirection: 'row',
|
||
justifyContent: 'space-between',
|
||
marginBottom: 20,
|
||
},
|
||
planCard: {
|
||
flex: 1,
|
||
marginHorizontal: 4,
|
||
borderRadius: 12,
|
||
borderWidth: 2,
|
||
borderColor: '#E0E0E0',
|
||
paddingVertical: 20,
|
||
paddingHorizontal: 12,
|
||
alignItems: 'center',
|
||
position: 'relative',
|
||
backgroundColor: 'white',
|
||
},
|
||
selectedPlan: {
|
||
borderColor: '#DF42D0',
|
||
backgroundColor: '#FFF5FE',
|
||
},
|
||
recommendedBadge: {
|
||
position: 'absolute',
|
||
top: -10,
|
||
backgroundColor: '#7B2CBF',
|
||
borderRadius: 10,
|
||
paddingHorizontal: 12,
|
||
paddingVertical: 4,
|
||
},
|
||
recommendedText: {
|
||
color: 'white',
|
||
fontSize: 10,
|
||
fontWeight: 'bold',
|
||
},
|
||
planHeader: {
|
||
alignItems: 'center',
|
||
marginBottom: 8,
|
||
},
|
||
planTitle: {
|
||
fontSize: 16,
|
||
fontWeight: '600',
|
||
color: '#333',
|
||
},
|
||
lifetimePlanTitle: {
|
||
color: '#7B2CBF',
|
||
},
|
||
quarterlyPlanTitle: {
|
||
color: '#DF42D0',
|
||
},
|
||
weeklyPlanTitle: {
|
||
color: '#FF9500',
|
||
},
|
||
planPricing: {
|
||
alignItems: 'center',
|
||
marginBottom: 8,
|
||
},
|
||
planPrice: {
|
||
fontSize: 24,
|
||
fontWeight: 'bold',
|
||
color: '#333',
|
||
},
|
||
lifetimePlanPrice: {
|
||
color: '#7B2CBF',
|
||
},
|
||
quarterlyPlanPrice: {
|
||
color: '#DF42D0',
|
||
},
|
||
weeklyPlanPrice: {
|
||
color: '#FF9500',
|
||
},
|
||
planSubtitle: {
|
||
fontSize: 12,
|
||
color: '#666',
|
||
textAlign: 'center',
|
||
lineHeight: 16,
|
||
},
|
||
purchaseButton: {
|
||
backgroundColor: '#DF42D0',
|
||
borderRadius: 25,
|
||
height: 50,
|
||
justifyContent: 'center',
|
||
alignItems: 'center',
|
||
marginBottom: 15,
|
||
marginTop: 10,
|
||
shadowColor: '#DF42D0',
|
||
shadowOffset: {
|
||
width: 0,
|
||
height: 4,
|
||
},
|
||
shadowOpacity: 0.3,
|
||
shadowRadius: 8,
|
||
elevation: 6,
|
||
},
|
||
disabledButton: {
|
||
backgroundColor: '#ccc',
|
||
shadowOpacity: 0,
|
||
elevation: 0,
|
||
},
|
||
purchaseButtonText: {
|
||
color: 'white',
|
||
fontSize: 18,
|
||
fontWeight: '600',
|
||
},
|
||
restoreButton: {
|
||
backgroundColor: 'transparent',
|
||
borderRadius: 25,
|
||
height: 50,
|
||
justifyContent: 'center',
|
||
alignItems: 'center',
|
||
marginBottom: 20,
|
||
},
|
||
restoreButtonText: {
|
||
color: '#666',
|
||
fontSize: 16,
|
||
fontWeight: '500',
|
||
},
|
||
disabledRestoreButton: {
|
||
opacity: 0.5,
|
||
},
|
||
restoreButtonContent: {
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
},
|
||
restoreButtonLoader: {
|
||
marginRight: 8,
|
||
},
|
||
loadingContainer: {
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
},
|
||
loadingSpinner: {
|
||
marginRight: 8,
|
||
},
|
||
disabledPlanCard: {
|
||
opacity: 0.5,
|
||
},
|
||
agreementRow: {
|
||
width: '100%',
|
||
marginTop: 6,
|
||
borderRadius: 12,
|
||
display: 'flex',
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
},
|
||
agreementPrefix: {
|
||
fontSize: 10,
|
||
color: '#333',
|
||
marginRight: 4,
|
||
},
|
||
agreementLink: {
|
||
fontSize: 10,
|
||
color: '#E91E63',
|
||
textDecorationLine: 'underline',
|
||
fontWeight: '500',
|
||
},
|
||
agreementSeparator: {
|
||
fontSize: 10,
|
||
color: '#666',
|
||
marginHorizontal: 2,
|
||
},
|
||
tipsContainer: {
|
||
borderRadius: 8,
|
||
},
|
||
tipsText: {
|
||
fontSize: 12,
|
||
color: '#666',
|
||
lineHeight: 18,
|
||
textAlign: 'center',
|
||
},
|
||
}); |