import { Image } from 'expo-image'; import { useRouter } from 'expo-router'; import React, { forwardRef, useImperativeHandle, useRef, useState } from 'react'; import { ActivityIndicator, Alert, Dimensions, Modal, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; import { applyOrder, getApplyResult, previewOrder } from '@/services/award'; import Alipay from 'expo-native-alipay'; import { LotteryResultModal, LotteryResultModalRef } from './LotteryResultModal'; const { width: SCREEN_WIDTH } = Dimensions.get('window'); // 等级配置 const LEVEL_MAP: Record = { A: { title: '超神款', color: '#FF4444' }, B: { title: '欧皇款', color: '#FF9600' }, C: { title: '隐藏款', color: '#9B59B6' }, D: { title: '普通款', color: '#666666' }, }; interface CheckoutModalProps { data: any; poolId: string; onSuccess: (param: { num: number; tradeNo: string }) => void; boxNumber?: string; } export interface CheckoutModalRef { show: (num: number, preview: any, boxNum?: string, seatNumbers?: number[], packFlag?: boolean) => void; showFreedom: () => void; close: () => void; } interface LotteryItem { id: string; name: string; cover: string; level: string; spu?: { marketPrice: number }; } // 自由购买数量选项 const FREEDOM_NUMS = [10, 20, 30, 40, 50]; export const CheckoutModal = forwardRef( ({ data, poolId, onSuccess, boxNumber }, ref) => { const router = useRouter(); const lotteryResultRef = useRef(null); const [visible, setVisible] = useState(false); const [num, setNum] = useState(1); const [checked, setChecked] = useState(true); const [loading, setLoading] = useState(false); const [freedomNum, setFreedomNum] = useState(10); const [freedomSelectVisible, setFreedomSelectVisible] = useState(false); // 预览数据 const [coin, setCoin] = useState(null); const [couponAmount, setCouponAmount] = useState(null); const [lastPrice, setLastPrice] = useState(null); const [magic, setMagic] = useState(null); const [cash, setCash] = useState(null); const [cashChecked, setCashChecked] = useState(false); // 盒子相关 const [boxNum, setBoxNum] = useState(boxNumber); const [seatNumbers, setSeatNumbers] = useState(); const [packFlag, setPackFlag] = useState(); // 抽奖结果 const [resultVisible, setResultVisible] = useState(false); const [resultLoading, setResultLoading] = useState(false); const [resultList, setResultList] = useState([]); // 设置预览数据 const setPreviewData = (previewData: any) => { setCoin(previewData.magicAmount || null); setCouponAmount(previewData.couponAmount || null); setLastPrice(previewData.paymentAmount); setMagic(previewData.magic || null); if (previewData.cash && previewData.cash.balance > previewData.paymentAmount) { setCash(previewData.cash); setCashChecked(true); } else { setCash(previewData.cash || null); setCashChecked(false); } }; useImperativeHandle(ref, () => ({ show: (n: number, previewData: any = {}, bNum?: string, seats?: number[], pack?: boolean) => { setNum(n); setBoxNum(bNum); setSeatNumbers(seats); setPackFlag(pack || undefined); setPreviewData(previewData); setVisible(true); }, showFreedom: () => { setFreedomNum(10); setFreedomSelectVisible(true); }, close: () => { setVisible(false); setFreedomSelectVisible(false); setResultVisible(false); }, })); const handleFreedomSelect = async (selectedNum: number) => { setFreedomSelectVisible(false); setLoading(true); try { const preview = await previewOrder(poolId, selectedNum); if (preview) { setNum(selectedNum); setFreedomNum(selectedNum); setPreviewData(preview); setVisible(true); } } catch (error: any) { Alert.alert('提示', error?.message || '获取订单信息失败'); } finally { setLoading(false); } }; const close = () => { setVisible(false); setFreedomSelectVisible(false); }; const closeResult = () => { setResultVisible(false); setResultList([]); onSuccess({ tradeNo: '', num }); }; // 支付 const pay = async () => { if (loading) return; if (!checked) { Alert.alert('提示', '请同意《宝箱服务协议》'); return; } setLoading(true); try { let paymentType = ''; // Prioritize Wallet if checked if (cashChecked) { paymentType = 'WALLET'; } else { // Default to Alipay for now as per branch logic paymentType = 'ALIPAY_APP'; } // If wallet is insufficient and logic requires mixed payment, it might be more complex // strictly following branch logic: if cashChecked use Wallet, else Alipay const payNum = packFlag ? 1 : num; const res = await applyOrder(poolId, payNum, paymentType, boxNum, seatNumbers, packFlag); // Handle Alipay Response if (res?.payInfo) { const result = await Alipay.pay(res.payInfo); console.log('Alipay Result:', result); const resultStatus = result?.resultStatus; if (resultStatus === '9000') { // Payment Success // Maybe verify payment here if needed, or just assume success const tradeNo = res.bizTradeNo || res.tradeNo; // Assuming tradeNo is in applyOrder response setVisible(false); router.push({ pathname: '/lottery' as any, params: { tradeNo, num, poolId } }); onSuccess({ tradeNo, num }); } else { Alert.alert('提示', '支付未完成'); } return; } if (res?.paySuccess || res?.bizTradeNo || res?.tradeNo) { const tradeNo = res.bizTradeNo || res.tradeNo; setVisible(false); // Navigation to Lottery Animation router.push({ pathname: '/lottery' as any, params: { tradeNo, num, poolId } }); // Trigger success callback (e.g. to refresh pool data) onSuccess({ tradeNo, num }); } else { Alert.alert('提示', res?.message || '支付失败,请重试'); } } catch (error: any) { console.error('Pay error:', error); Alert.alert('支付失败', error?.message || '请稍后重试'); } finally { setLoading(false); } }; // 获取抽奖结果(10发以下用弹窗) const fetchLotteryResult = async (tradeNo: string) => { setResultLoading(true); setResultVisible(true); setResultList([]); let attempts = 0; const maxAttempts = 5; const poll = async () => { try { const res = await getApplyResult(tradeNo); if (res?.inventoryList && res.inventoryList.length > 0) { setResultList(res.inventoryList); setResultLoading(false); // 不在这里调用 onSuccess,等用户关闭弹窗时再调用 } else if (attempts < maxAttempts) { attempts++; setTimeout(poll, 1000); } else { setResultLoading(false); if (typeof window !== 'undefined') { Alert.alert('提示', '获取结果超时,请在仓库中查看'); } } } catch { if (attempts < maxAttempts) { attempts++; setTimeout(poll, 1000); } else { setResultLoading(false); if (typeof window !== 'undefined') { Alert.alert('提示', '获取结果失败,请在仓库中查看'); } } } }; poll(); }; const displayPrice = lastPrice ?? (data?.price || 0) * num; return ( <> {/* 10发以上的全屏抽奖结果弹窗 */} { // 抽奖结果弹窗关闭后刷新数据 onSuccess({ tradeNo: '', num }); }} onGoStore={() => { onSuccess({ tradeNo: '', num }); router.replace('/store' as any); }} /> {/* 自由购买数量选择弹窗 */} setFreedomSelectVisible(false)}> setFreedomSelectVisible(false)} /> 购买多盒 setFreedomSelectVisible(false)} style={styles.closeBtn}> × {FREEDOM_NUMS.map((item) => ( setFreedomNum(item)} > {item} {freedomNum === item && } ))} handleFreedomSelect(freedomNum)} disabled={loading} > {loading ? ( ) : ( 确认 ¥{(data?.price || 0) * freedomNum} )} {/* 支付确认弹窗 */} {data?.name} × 购买件数 ¥{data?.price} x {num} 优惠券 {couponAmount ? `已使用优惠¥${couponAmount}` : '暂无优惠券可选'} {magic && magic.balance > 0 && ( 果实 (剩余:{magic.balance}) 已抵扣 ¥{coin || 0} )} {cash && ( 钱包支付 (余额:¥{cash.balance}) setCashChecked(!cashChecked)} > {cashChecked && } )} 我已满18周岁,已阅读并同意 { setVisible(false); router.push({ pathname: '/agreement', params: { type: 'magic.html' } }); }}> 《宝箱服务协议》 宝箱商品存在概率性,请谨慎消费 setChecked(!checked)} > {checked && } 实付: ¥{displayPrice.toFixed(2)} {loading ? : 立即支付} {/* 抽奖结果弹窗 */} 🎉 恭喜您获得 🎉 × {resultLoading ? ( 正在开启宝箱... ) : ( {resultList.map((item, index) => ( {LEVEL_MAP[item.level]?.title || item.level} {item.name} {item.spu?.marketPrice && ( 参考价:¥{item.spu.marketPrice} )} ))} )} 继续抽奖 ); } ); const styles = StyleSheet.create({ overlay: { flex: 1, backgroundColor: 'rgba(0,0,0,0.5)', justifyContent: 'flex-end', }, mask: { flex: 1 }, container: { backgroundColor: '#fff', borderTopLeftRadius: 20, borderTopRightRadius: 20, paddingBottom: 34, }, header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', padding: 15, borderBottomWidth: 1, borderBottomColor: '#eee', position: 'relative', }, title: { fontSize: 16, fontWeight: '600', color: '#333' }, closeBtn: { position: 'absolute', right: 15, top: 10 }, closeText: { fontSize: 24, color: '#999' }, content: { padding: 15 }, row: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingVertical: 10, }, rowLeft: { flexDirection: 'row', alignItems: 'center', flex: 1 }, label: { fontSize: 14, color: '#333' }, priceText: { fontSize: 14, color: '#ff9600', fontWeight: '600' }, valueText: { fontSize: 12, color: '#999' }, themeColor: { color: '#ff9600' }, balanceText: { fontSize: 12, color: '#999', marginLeft: 5 }, radio: { width: 20, height: 20, borderRadius: 10, borderWidth: 2, borderColor: '#ddd', justifyContent: 'center', alignItems: 'center', }, radioChecked: { borderColor: '#ff9600' }, radioInner: { width: 10, height: 10, borderRadius: 5, backgroundColor: '#ff9600' }, agreementRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', paddingVertical: 10, marginTop: 10, }, agreementLeft: { flex: 1, marginRight: 10 }, agreementTextContainer: { flexDirection: 'row', alignItems: 'center', flexWrap: 'wrap' }, agreementText: { fontSize: 12, color: '#333', lineHeight: 18 }, link: { color: '#ff9600' }, tips: { fontSize: 11, color: '#999', marginTop: 5 }, footer: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 15, paddingTop: 15, borderTopWidth: 1, borderTopColor: '#eee', }, priceInfo: { flexDirection: 'row', alignItems: 'center' }, totalLabel: { fontSize: 14, color: '#333' }, totalPrice: { fontSize: 20, color: '#ff9600', fontWeight: 'bold' }, payBtn: { backgroundColor: '#ff9600', paddingHorizontal: 30, paddingVertical: 12, borderRadius: 25, }, payBtnDisabled: { opacity: 0.6 }, payBtnText: { color: '#fff', fontSize: 16, fontWeight: '600' }, // 自由购买弹窗 freedomContainer: { backgroundColor: '#fff', borderTopLeftRadius: 20, borderTopRightRadius: 20, paddingBottom: 34, }, freedomContent: { padding: 20 }, freedomBtnList: { flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'space-between' }, freedomBtn: { width: '48%', height: 50, backgroundColor: '#fff', borderRadius: 8, justifyContent: 'center', alignItems: 'center', marginBottom: 12, borderWidth: 1, borderColor: '#eee', position: 'relative', }, freedomBtnActive: { backgroundColor: '#F1423D', borderColor: '#F1423D' }, freedomBtnText: { fontSize: 18, fontWeight: 'bold', color: '#333' }, freedomBtnTextActive: { color: '#fff' }, freedomUnit: { fontSize: 12, fontWeight: '500' }, checkIcon: { position: 'absolute', bottom: 0, right: 0, backgroundColor: '#fff', color: '#F1423D', fontSize: 12, paddingHorizontal: 6, paddingVertical: 2, borderTopLeftRadius: 8, borderBottomRightRadius: 8, }, freedomSubmitBtn: { backgroundColor: '#ff9600', height: 50, borderRadius: 25, justifyContent: 'center', alignItems: 'center', marginTop: 20, }, freedomSubmitText: { color: '#fff', fontSize: 16, fontWeight: '600' }, // 抽奖结果弹窗 resultOverlay: { flex: 1, backgroundColor: 'rgba(0,0,0,0.7)', justifyContent: 'center', alignItems: 'center', padding: 20, }, resultContainer: { width: '100%', maxHeight: '80%', backgroundColor: '#fff', borderRadius: 16, overflow: 'hidden', }, resultHeader: { alignItems: 'center', padding: 20, backgroundColor: '#ff9600', position: 'relative', }, resultTitle: { fontSize: 20, fontWeight: 'bold', color: '#fff', }, resultCloseBtn: { position: 'absolute', right: 15, top: 15, width: 30, height: 30, backgroundColor: 'rgba(255,255,255,0.3)', borderRadius: 15, justifyContent: 'center', alignItems: 'center', }, resultLoading: { padding: 60, alignItems: 'center', }, resultLoadingText: { marginTop: 15, fontSize: 14, color: '#666', }, resultScroll: { maxHeight: 400, }, resultList: { flexDirection: 'row', flexWrap: 'wrap', padding: 10, justifyContent: 'space-between', }, resultItem: { width: (SCREEN_WIDTH - 80) / 2, backgroundColor: '#f9f9f9', borderRadius: 10, padding: 10, marginBottom: 10, alignItems: 'center', }, levelBadge: { paddingHorizontal: 10, paddingVertical: 3, borderRadius: 10, marginBottom: 8, }, levelText: { color: '#fff', fontSize: 11, fontWeight: 'bold', }, resultImageBox: { width: '100%', aspectRatio: 1, backgroundColor: '#fff', borderRadius: 8, overflow: 'hidden', }, resultImage: { width: '100%', height: '100%', }, resultName: { fontSize: 12, color: '#333', textAlign: 'center', marginTop: 8, lineHeight: 16, }, resultPrice: { fontSize: 10, color: '#999', marginTop: 4, }, resultFooter: { padding: 15, borderTopWidth: 1, borderTopColor: '#eee', }, resultBtn: { backgroundColor: '#ff9600', height: 46, borderRadius: 23, justifyContent: 'center', alignItems: 'center', }, resultBtnText: { color: '#fff', fontSize: 16, fontWeight: '600', }, });