import { useState, useRef, useMemo, useCallback, useEffect } from 'react' import { View, Text, StyleSheet, Pressable, ActivityIndicator, Platform, } from 'react-native' import { LinearGradient } from 'expo-linear-gradient' import { useSafeAreaInsets } from 'react-native-safe-area-context' import { useRouter } from 'expo-router' import { useTranslation } from 'react-i18next' import BottomSheet, { BottomSheetView, BottomSheetBackdrop } from '@gorhom/bottom-sheet' import { root } from '@repo/core' import { AlipayController } from '@repo/sdk' import { CloseIcon, CheckIcon, UncheckedIcon, PointsIcon } from '@/components/icon' import Toast from '@/components/ui/Toast' import { useUserBalanceStore } from '@/stores/userBalanceStore' // 动态导入支付宝 SDK(需要安装 expo-native-alipay) let Alipay: any = null try { Alipay = require('expo-native-alipay').default } catch (e) { console.warn('expo-native-alipay not installed, payment will not work') } export interface TopUpOption { id: string points: number price: number } export interface TopUpDrawerProps { /** * 是否显示抽屉 */ visible: boolean /** * 关闭回调 */ onClose: () => void /** * 需要消耗的积分 */ requiredPoints?: number /** * 当前剩余积分 */ remainingPoints?: number /** * 充值选项列表 */ options?: TopUpOption[] /** * 确认充值回调 */ onConfirm?: (option: TopUpOption) => void /** * 充值标题 */ topUpTitle?: string /** * 充值描述 */ topUpDescription?: string /** * 导航回调,用于在导航时关闭父级抽屉(如 PointsDrawer) */ onNavigate?: () => void } const defaultOptions: TopUpOption[] = [ { id: '1', points: 1000, price: 20 }, { id: '2', points: 2500, price: 20 }, { id: '3', points: 5000, price: 20 }, { id: '4', points: 10000, price: 20 }, ] // 支付宝回调 scheme(需要与 app.json 中配置一致) const ALIPAY_SCHEME = 'popcore' export default function TopUpDrawer({ visible, onClose, options = defaultOptions, onConfirm, topUpTitle, topUpDescription, onNavigate, }: TopUpDrawerProps) { const { t } = useTranslation() const router = useRouter() const insets = useSafeAreaInsets() const bottomSheetRef = useRef(null) const [selectedOption, setSelectedOption] = useState( options[0] || null ) const [agreed, setAgreed] = useState(false) const [loading, setLoading] = useState(false) // 获取余额 store 的方法 const { load: loadBalance, restartPolling } = useUserBalanceStore() const snapPoints = useMemo(() => [500], []) useEffect(() => { if (visible) { bottomSheetRef.current?.expand() } else { bottomSheetRef.current?.close() } }, [visible]) const handleSheetChanges = useCallback((index: number) => { if (index === -1) { onClose() } }, [onClose]) const renderBackdrop = useCallback( (props: any) => ( ), [] ) const handleClose = useCallback(() => { bottomSheetRef.current?.close() onClose() }, [onClose]) // 如果没有传入标题,使用默认翻译 const displayTitle = topUpTitle || t('topUp.title') // 初始化支付宝 SDK useEffect(() => { if (Alipay && Platform.OS === 'ios') { Alipay.setAlipayScheme(ALIPAY_SCHEME) } }, []) const handleConfirm = async () => { if (!selectedOption || !agreed) return // 检查支付宝 SDK 是否可用 if (!Alipay) { Toast.show(t('topUp.alipayNotInstalled') || '支付宝 SDK 未安装') return } setLoading(true) try { // 1. 调用后端 API 创建订单 const alipay = root.get(AlipayController) const response = await alipay.preRecharge({ credits: selectedOption.points, }) if (!response?.orderStr) { Toast.show(t('topUp.createOrderFailed') || '创建订单失败') setLoading(false) return } // 2. 调用支付宝 SDK 发起支付 const result = await Alipay.pay(response.orderStr) console.log('Alipay payment result:', result) // 3. 处理支付结果 if (result.resultStatus === '9000') { // 支付成功 Toast.show(t('topUp.paymentSuccess') || '支付成功!积分正在到账中...') // 刷新余额 loadBalance(true) restartPolling() // 关闭抽屉 onClose() // 调用外部回调 onConfirm?.(selectedOption) } else if (result.resultStatus === '6001') { // 用户取消 Toast.show(t('topUp.paymentCancelled') || '支付已取消') } else { // 支付失败 Toast.show(t('topUp.paymentFailed') || '支付失败,请重试') } } catch (err: any) { console.error('Payment error:', err) Toast.show(err?.message || t('topUp.paymentError') || '支付出错') } finally { setLoading(false) // 无论成功失败都刷新余额 loadBalance(true) } } return ( {displayTitle && ( // 这个绝对定位的标题层会盖在右上角关闭按钮上,必须允许触摸事件“穿透” {/* 主文字层 */} {displayTitle} )} {topUpDescription && ( {topUpDescription} )} {/* 充值选项网格 */} {options.map((option) => { const isSelected = selectedOption?.id === option.id return ( setSelectedOption(option)} > {isSelected ? ( {option.points.toLocaleString()} ${option.price} ) : ( {option.points.toLocaleString()} ${option.price} )} ) })} {/* 底部按钮和协议 */} {loading ? ( ) : ( {t('topUp.confirm')} )} setAgreed(!agreed)} > {agreed ? : } setAgreed(!agreed)} > {t('topUp.agreementText')}{' '} { e.stopPropagation() onClose() onNavigate?.() router.push('/terms') }} > {t('topUp.terms')} {t('topUp.agreementAnd')} { e.stopPropagation() onClose() onNavigate?.() router.push('/privacy') }} > {t('topUp.privacy')} ) } const styles = StyleSheet.create({ bottomSheetBackground: { backgroundColor: '#090A0B', }, handleIndicator: { backgroundColor: '#666666', }, container: { backgroundColor: '#090A0B', paddingHorizontal: 12, overflow: 'visible', }, titleContainer: { position: 'absolute', top:10, left: 0, right: 0, alignItems: 'center', justifyContent: 'center', zIndex: 10, height: 40, overflow: 'visible', }, strokeTextWrapper: { position: 'absolute', alignItems: 'center', justifyContent: 'center', }, titleText: { fontSize: 24, fontWeight: '600', textAlign: 'center', zIndex: 1, includeFontPadding: false, textAlignVertical: 'center', lineHeight: 28, }, titleStroke: { color: '#000000', }, titleFill: { color: '#F5F5F5', position: 'relative', }, header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingTop: 12, marginTop: 20, }, closeButton: { width: 24, height: 24, alignItems: 'center', justifyContent: 'center', }, infoSection: { marginBottom: 24, }, infoText: { color: '#F5F5F5', fontSize: 14, fontWeight: '400', marginBottom: 8, }, subtitle: { color: '#ABABAB', fontSize: 12, fontWeight: '400', }, optionsGrid: { flexDirection: 'row', flexWrap: 'wrap', gap: 8, marginTop: 12, }, optionCardWrapper: { width: '47%', aspectRatio: 2.3, }, optionCardGradient: { width: '100%', height: '100%', borderRadius: 12, padding: 2, }, optionCard: { flex: 1, alignItems: 'center', justifyContent: 'center', borderRadius: 12, backgroundColor: '#16181B', overflow: 'hidden', gap: 4, }, optionContent: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 2, }, optionPoints: { color: '#F5F5F5', fontSize: 20, fontWeight: '500', }, optionPrice: { color: '#ABABAB', fontSize: 12, fontWeight: '400', }, footer: { paddingTop: 20, gap: 16, }, confirmButton: { width: '100%', height: 48, borderRadius: 12, overflow: 'hidden', }, confirmButtonGradient: { width: '100%', height: 48, alignItems: 'center', justifyContent: 'center', borderRadius: 12, }, confirmButtonDisabled: { opacity: 0.5, }, confirmButtonText: { color: '#F5F5F5', fontSize: 16, fontWeight: '600', }, agreementContainer: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', }, checkbox: { width: 12, height: 12, alignItems: 'center', justifyContent: 'center', marginRight: 4, }, agreementText: { color: '#8A8A8A', fontSize: 10, fontWeight: '400', }, agreementLink: { color: '#ABABAB', textDecorationLine: 'underline', }, })