import { Ionicons } from '@expo/vector-icons';
import { useLocalSearchParams, useRouter } from 'expo-router';
import { useCallback, useState } from 'react';
import { ActivityIndicator, Alert, Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { useLegalData, useLegalMembership } from '../../src/hooks';
import { LoadingState, ErrorState, EmptyState } from '../../src/components/StateView';
import { colors } from '../../src/theme';
import type { LegalPaymentChannel } from '../../src/types';
/* ===================== 付款方式 ===================== */
const CHANNELS: { id: LegalPaymentChannel; label: string; icon: keyof typeof Ionicons.glyphMap; color: string }[] = [
{ id: 'wechat', label: '微信支付', icon: 'logo-wechat', color: colors.success },
{ id: 'alipay', label: '支付宝', icon: 'wallet-outline', color: colors.primary },
];
/* ===================== 顶部导航 ===================== */
function Header({ onBack }: { onBack: () => void }) {
return (
确认支付
);
}
/* ===================== 页面 ===================== */
export default function LegalCheckoutScreen() {
const router = useRouter();
const { planId } = useLocalSearchParams();
const id = Array.isArray(planId) ? planId[0] : planId;
const { plans, loading, error, refresh } = useLegalData();
const { createOrder, payOrder, processing } = useLegalMembership();
const [channel, setChannel] = useState('wechat');
const [paid, setPaid] = useState(false);
const plan = plans.find((p) => p.id === id);
const handlePay = useCallback(async () => {
if (!plan) return;
try {
const order = await createOrder(plan.id, channel);
const result = await payOrder(order.id);
if (result.status === 'paid') {
setPaid(true);
} else {
Alert.alert('支付失败', '请稍后重试');
}
} catch (e) {
Alert.alert('支付失败', (e as Error).message || '请稍后重试');
}
}, [plan, channel, createOrder, payOrder]);
// 加载套餐中
if (loading) {
return (
);
}
// 接口失败:必须先于「套餐不存在」判断,否则取数失败会伪装成业务空态,
// 把用户引向「从会员套餐页重新进入」这条无效的自救路径。
if (error) {
return (
);
}
// 套餐不存在(异常入口)
if (!plan) {
return (
);
}
// 支付成功
if (paid) {
return (
router.replace('/legal')} />
支付成功
已开通{plan.name},可畅享会员专属法律服务
router.replace('/legal')}>
返回法律服务
);
}
return (
router.back()} />
{/* 套餐摘要 */}
{plan.name}
¥
{plan.price}
{plan.period}
{plan.benefits.map((benefit, idx) => (
{benefit}
))}
{/* 支付方式 */}
选择支付方式
{CHANNELS.map((c) => {
const active = c.id === channel;
return (
[
styles.channelRow,
active && styles.channelRowActive,
pressed && styles.channelPressed,
]}
onPress={() => setChannel(c.id)}
>
{c.label}
{active && }
);
})}
{/* 实付金额 */}
实付金额
¥
{plan.price}
{/* 确认支付(底部固定) */}
[styles.payBtn, pressed && styles.payBtnPressed, processing && styles.payBtnDisabled]}
onPress={handlePay}
disabled={processing}
>
{processing ? (
) : (
确认支付 ¥{plan.price}
)}
);
}
const styles = StyleSheet.create({
safeArea: {
flex: 1,
backgroundColor: colors.pageBg,
},
header: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingHorizontal: 8,
height: 48,
backgroundColor: colors.white,
borderBottomWidth: 1,
borderBottomColor: colors.borderLight,
},
headerBtn: {
width: 40,
height: 40,
alignItems: 'center',
justifyContent: 'center',
},
headerTitle: {
fontSize: 17,
fontWeight: '700',
color: colors.textPrimary,
},
scroll: {
flex: 1,
},
content: {
padding: 8,
paddingBottom: 24,
},
summaryCard: {
padding: 8,
borderRadius: 8,
backgroundColor: colors.white,
borderWidth: 1,
borderColor: colors.border,
},
summaryName: {
fontSize: 16,
fontWeight: '700',
color: colors.textPrimary,
},
summaryPriceRow: {
flexDirection: 'row',
alignItems: 'baseline',
marginTop: 8,
marginBottom: 12,
},
summaryCurrency: {
fontSize: 15,
fontWeight: '700',
color: colors.actionText,
},
summaryPrice: {
fontSize: 30,
fontWeight: '700',
color: colors.actionText,
},
summaryPeriod: {
fontSize: 13,
color: colors.textSecondary,
marginLeft: 2,
},
benefits: {
gap: 6,
},
benefitRow: {
flexDirection: 'row',
alignItems: 'center',
gap: 6,
},
benefitText: {
fontSize: 12,
color: colors.textSecondary,
},
sectionTitle: {
marginTop: 20,
marginBottom: 10,
fontSize: 15,
fontWeight: '700',
color: colors.textPrimary,
},
channelGroup: {
gap: 8,
},
channelRow: {
flexDirection: 'row',
alignItems: 'center',
padding: 8,
borderRadius: 8,
backgroundColor: colors.white,
borderWidth: 1,
borderColor: colors.border,
gap: 12,
},
channelRowActive: {
borderColor: colors.action,
backgroundColor: colors.actionLight,
},
channelPressed: {
opacity: 0.85,
},
channelLabel: {
flex: 1,
fontSize: 15,
fontWeight: '600',
color: colors.textPrimary,
},
radio: {
width: 20,
height: 20,
borderRadius: 10,
borderWidth: 2,
borderColor: colors.border,
alignItems: 'center',
justifyContent: 'center',
},
radioActive: {
borderColor: colors.action,
},
radioDot: {
width: 10,
height: 10,
borderRadius: 5,
backgroundColor: colors.action,
},
amountRow: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
marginTop: 20,
paddingTop: 16,
borderTopWidth: 1,
borderTopColor: colors.borderLight,
},
amountLabel: {
fontSize: 14,
color: colors.textSecondary,
},
amountValue: {
flexDirection: 'row',
alignItems: 'baseline',
},
amountCurrency: {
fontSize: 15,
fontWeight: '700',
color: colors.actionText,
},
amountPrice: {
fontSize: 22,
fontWeight: '700',
color: colors.actionText,
},
footer: {
padding: 12,
backgroundColor: colors.white,
borderTopWidth: 1,
borderTopColor: colors.borderLight,
},
payBtn: {
height: 48,
borderRadius: 8,
backgroundColor: colors.primaryBtn,
alignItems: 'center',
justifyContent: 'center',
},
payBtnPressed: {
opacity: 0.9,
},
payBtnDisabled: {
opacity: 0.6,
},
payBtnText: {
fontSize: 16,
fontWeight: '700',
color: colors.white,
},
successBox: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
padding: 24,
},
successIconWrap: {
marginBottom: 16,
},
successTitle: {
fontSize: 20,
fontWeight: '700',
color: colors.textPrimary,
},
successDesc: {
marginTop: 8,
fontSize: 14,
color: colors.textSecondary,
textAlign: 'center',
},
successBtn: {
marginTop: 28,
width: 200,
height: 46,
borderRadius: 23,
backgroundColor: colors.primaryBtn,
alignItems: 'center',
justifyContent: 'center',
},
successBtnText: {
fontSize: 15,
fontWeight: '700',
color: colors.white,
},
});