import {useEffect, useState} from "react"; import { Image, Button, Cell, CellGroup, Input, Space, ActionSheet, Popup, InputNumber, ConfigProvider } from '@nutui/nutui-react-taro' import {Location, ArrowRight} from '@nutui/icons-react-taro' import Taro, {useDidShow} from '@tarojs/taro' import {ShopGoods} from "@/api/shop/shopGoods/model"; import {getShopGoods} from "@/api/shop/shopGoods"; import {View, Text} from '@tarojs/components'; import {listShopUserAddress} from "@/api/shop/shopUserAddress"; import {ShopUserAddress} from "@/api/shop/shopUserAddress/model"; import './index.scss' import Gap from "@/components/Gap"; import {selectPayment} from "@/api/system/payment"; import {Payment} from "@/api/system/payment/model"; import {PaymentHandler, PaymentType, buildSingleGoodsOrder} from "@/utils/payment"; import OrderConfirmSkeleton from "@/components/OrderConfirmSkeleton"; import CouponList from "@/components/CouponList"; import {CouponCardProps} from "@/components/CouponCard"; import {getMyAvailableCoupons} from "@/api/shop/shopUserCoupon"; import { transformCouponData, calculateCouponDiscount, isCouponUsable, getCouponUnusableReason, sortCoupons, filterUsableCoupons, filterUnusableCoupons } from "@/utils/couponUtils"; import navTo from "@/utils/common"; const OrderConfirm = () => { const [goods, setGoods] = useState(null); const [address, setAddress] = useState() const [payments, setPayments] = useState([]) const [payment, setPayment] = useState() const [isVisible, setIsVisible] = useState(false) const [quantity, setQuantity] = useState(1) const [orderRemark, setOrderRemark] = useState('') const [loading, setLoading] = useState(true) const [error, setError] = useState('') const [payLoading, setPayLoading] = useState(false) // InputNumber 主题配置 const customTheme = { nutuiInputnumberButtonWidth: '28px', nutuiInputnumberButtonHeight: '28px', nutuiInputnumberInputWidth: '40px', nutuiInputnumberInputHeight: '28px', nutuiInputnumberInputBorderRadius: '4px', nutuiInputnumberButtonBorderRadius: '4px', } // 优惠券相关状态 const [selectedCoupon, setSelectedCoupon] = useState(null) const [couponVisible, setCouponVisible] = useState(false) const [availableCoupons, setAvailableCoupons] = useState([]) const [couponLoading, setCouponLoading] = useState(false) const router = Taro.getCurrentInstance().router; const goodsId = router?.params?.goodsId; // 计算商品总价 const getGoodsTotal = () => { if (!goods) return 0 const price = parseFloat(goods.price || '0') // const total = price * quantity // 🔍 详细日志,用于排查数值精度问题 // console.log('💵 商品总价计算:', { // goodsPrice: goods.price, // goodsPriceType: typeof goods.price, // parsedPrice: price, // quantity: quantity, // total: total, // totalFixed2: total.toFixed(2), // totalString: total.toString() // }) return price * quantity } // 计算优惠券折扣 const getCouponDiscount = () => { if (!selectedCoupon || !goods) return 0 const total = getGoodsTotal() return calculateCouponDiscount(selectedCoupon, total) } // 计算实付金额 const getFinalPrice = () => { const total = getGoodsTotal() const discount = getCouponDiscount() return Math.max(0, total - discount) } const handleSelect = (item: any) => { setPayment(payments.find(payment => payment.name === item.name)) setIsVisible(false) } // 处理数量变化 const handleQuantityChange = (value: string | number) => { const newQuantity = typeof value === 'string' ? parseInt(value) || 1 : value const finalQuantity = Math.max(1, Math.min(newQuantity, goods?.stock || 999)) setQuantity(finalQuantity) // 数量变化时,重新排序优惠券并检查当前选中的优惠券是否还可用 if (availableCoupons.length > 0) { const newTotal = parseFloat(goods?.price || '0') * finalQuantity const sortedCoupons = sortCoupons(availableCoupons, newTotal) const usableCoupons = filterUsableCoupons(sortedCoupons, newTotal) setAvailableCoupons(sortedCoupons) // 检查当前选中的优惠券是否还可用 if (selectedCoupon && !isCouponUsable(selectedCoupon, newTotal)) { setSelectedCoupon(null) Taro.showToast({ title: '当前优惠券不满足使用条件,已自动取消', icon: 'none' }) // 🎯 自动推荐新的最优优惠券 if (usableCoupons.length > 0) { const bestCoupon = usableCoupons[0] const discount = calculateCouponDiscount(bestCoupon, newTotal) if (discount > 0) { setSelectedCoupon(bestCoupon) Taro.showToast({ title: `已为您重新推荐最优优惠券,可省¥${discount.toFixed(2)}`, icon: 'success', duration: 3000 }) } } } else if (!selectedCoupon && usableCoupons.length > 0) { // 🔔 如果没有选中优惠券但有可用的,推荐最优的 const bestCoupon = usableCoupons[0] const discount = calculateCouponDiscount(bestCoupon, newTotal) if (discount > 0) { setSelectedCoupon(bestCoupon) Taro.showToast({ title: `已为您推荐最优优惠券,可省¥${discount.toFixed(2)}`, icon: 'success', duration: 3000 }) } } else if (selectedCoupon && usableCoupons.length > 0) { // 🔍 检查是否有更好的优惠券 const bestCoupon = usableCoupons[0] const currentDiscount = calculateCouponDiscount(selectedCoupon, newTotal) const bestDiscount = calculateCouponDiscount(bestCoupon, newTotal) // 如果有更好的优惠券(优惠超过0.01元) if (bestDiscount > currentDiscount + 0.01 && bestCoupon.id !== selectedCoupon.id) { Taro.showModal({ title: '发现更优惠的优惠券', content: `有更好的优惠券可用,额外节省¥${(bestDiscount - currentDiscount).toFixed(2)},是否更换?`, success: (res) => { if (res.confirm) { setSelectedCoupon(bestCoupon) Taro.showToast({ title: '优惠券已更换', icon: 'success' }) } } }) } } } } // 处理优惠券选择 const handleCouponSelect = (coupon: CouponCardProps) => { const total = getGoodsTotal() // 🔍 详细日志记录,用于排查问题 console.log('🎫 手动选择优惠券详细信息:', { coupon: { id: coupon.id, title: coupon.title, type: coupon.type, amount: coupon.amount, minAmount: coupon.minAmount, status: coupon.status }, orderInfo: { goodsPrice: goods?.price, quantity: quantity, total: total, totalFixed: total.toFixed(2) }, validation: { isUsable: isCouponUsable(coupon, total), discount: calculateCouponDiscount(coupon, total), reason: getCouponUnusableReason(coupon, total) } }) // 检查是否可用 if (!isCouponUsable(coupon, total)) { const reason = getCouponUnusableReason(coupon, total) // 🚨 记录手动选择失败的详细信息 console.error('🚨 手动选择优惠券失败:', { reason, coupon, total, minAmount: coupon.minAmount, comparison: { totalVsMinAmount: `${total} < ${coupon.minAmount}`, result: total < (coupon.minAmount || 0) } }) Taro.showToast({ title: reason || '优惠券不可用', icon: 'none' }) return } setSelectedCoupon(coupon) setCouponVisible(false) Taro.showToast({ title: '优惠券选择成功', icon: 'success' }) } // 取消选择优惠券 const handleCouponCancel = () => { setSelectedCoupon(null) Taro.showToast({ title: '已取消使用优惠券', icon: 'success' }) } // 加载用户优惠券 const loadUserCoupons = async () => { try { setCouponLoading(true) // 使用新的API获取可用优惠券 const res = await getMyAvailableCoupons() if (res && res.length > 0) { // 转换数据格式 const transformedCoupons = res.map(transformCouponData) // 按优惠金额排序 const total = getGoodsTotal() const sortedCoupons = sortCoupons(transformedCoupons, total) const usableCoupons = filterUsableCoupons(sortedCoupons, total) setAvailableCoupons(sortedCoupons) // 🎯 智能推荐:自动应用最优惠的可用优惠券 if (usableCoupons.length > 0 && !selectedCoupon) { const bestCoupon = usableCoupons[0] // 已经按优惠金额排序,第一个就是最优的 const discount = calculateCouponDiscount(bestCoupon, total) // 🔍 详细日志记录自动推荐的信息 console.log('🤖 自动推荐优惠券详细信息:', { coupon: { id: bestCoupon.id, title: bestCoupon.title, type: bestCoupon.type, amount: bestCoupon.amount, minAmount: bestCoupon.minAmount, status: bestCoupon.status }, orderInfo: { goodsPrice: goods?.price, quantity: quantity, total: total, totalFixed: total.toFixed(2) }, validation: { isUsable: isCouponUsable(bestCoupon, total), discount: discount, reason: getCouponUnusableReason(bestCoupon, total) } }) if (discount > 0) { setSelectedCoupon(bestCoupon) // 显示智能推荐提示 Taro.showToast({ title: `已为您推荐最优优惠券,可省¥${discount.toFixed(2)}`, icon: 'success', duration: 3000 }) } } // 🔔 优惠券提示:如果有可用优惠券,显示提示 if (usableCoupons.length > 0) { console.log(`发现${usableCoupons.length}张可用优惠券,已为您推荐最优惠券`) } console.log('加载优惠券成功:', { originalData: res, transformedData: transformedCoupons, sortedData: sortedCoupons, usableCoupons: usableCoupons, recommendedCoupon: usableCoupons[0] || null }) } else { setAvailableCoupons([]) console.log('暂无可用优惠券') } } catch (error) { console.error('加载优惠券失败:', error) setAvailableCoupons([]) Taro.showToast({ title: '加载优惠券失败', icon: 'none' }) } finally { setCouponLoading(false) } } /** * 统一支付入口 */ const onPay = async (goods: ShopGoods) => { try { setPayLoading(true) // 基础校验 if (!address) { Taro.showToast({ title: '请选择收货地址', icon: 'error' }) return; } if (!payment) { Taro.showToast({ title: '请选择支付方式', icon: 'error' }) return; } // 库存校验 if (goods.stock !== undefined && quantity > goods.stock) { Taro.showToast({ title: '商品库存不足', icon: 'error' }) return; } // 优惠券校验 if (selectedCoupon) { const total = getGoodsTotal() if (!isCouponUsable(selectedCoupon, total)) { const reason = getCouponUnusableReason(selectedCoupon, total) Taro.showToast({ title: reason || '优惠券不可用', icon: 'error' }) return; } } else { // 🔔 支付前最后一次检查:提醒用户是否有可用优惠券 const total = getGoodsTotal() const usableCoupons = filterUsableCoupons(availableCoupons, total) if (usableCoupons.length > 0) { const bestCoupon = usableCoupons[0] const discount = calculateCouponDiscount(bestCoupon, total) if (discount > 0) { // 用模态框提醒用户 const confirmResult = await new Promise((resolve) => { Taro.showModal({ title: '发现可用优惠券', content: `您有优惠券可使用,可省¥${discount.toFixed(2)},是否使用?`, success: (res) => resolve(res.confirm), fail: () => resolve(false) }) }) if (confirmResult) { setSelectedCoupon(bestCoupon) // 🔄 使用优惠券后需要重新构建订单数据,这里直接递归调用支付函数 // 但要确保传递最新的优惠券信息 const currentPaymentType = payment.type === 0 ? PaymentType.BALANCE : PaymentType.WECHAT; const updatedOrderData = buildSingleGoodsOrder( goods.goodsId!, quantity, address.id, { comments: goods.name, deliveryType: 0, buyerRemarks: orderRemark, couponId: parseInt(String(bestCoupon.id), 10) } ); console.log('🎯 使用推荐优惠券的订单数据:', updatedOrderData); // 执行支付 await PaymentHandler.pay(updatedOrderData, currentPaymentType); return; // 提前返回,避免重复执行支付 } else { // 用户选择不使用优惠券,继续支付 } } } } // 构建订单数据 const orderData = buildSingleGoodsOrder( goods.goodsId!, quantity, address.id, { comments: goods.name, deliveryType: 0, buyerRemarks: orderRemark, // 🔧 确保 couponId 是正确的数字类型,且不传递 undefined couponId: selectedCoupon ? parseInt(String(selectedCoupon.id), 10) : undefined } ); // 根据支付方式选择支付类型 const paymentType = payment.type === 0 ? PaymentType.BALANCE : PaymentType.WECHAT; // 🔍 支付前的详细信息记录 console.log('💰 开始支付 - 详细信息:', { orderData, paymentType, selectedCoupon: selectedCoupon ? { id: selectedCoupon.id, title: selectedCoupon.title, type: selectedCoupon.type, amount: selectedCoupon.amount, minAmount: selectedCoupon.minAmount, discount: getCouponDiscount() } : null, priceCalculation: { goodsPrice: goods?.price, quantity: quantity, goodsTotal: getGoodsTotal(), couponDiscount: getCouponDiscount(), finalPrice: getFinalPrice() }, couponValidation: selectedCoupon ? { isUsable: isCouponUsable(selectedCoupon, getGoodsTotal()), reason: getCouponUnusableReason(selectedCoupon, getGoodsTotal()) } : null }); // 执行支付 - 移除这里的成功提示,让PaymentHandler统一处理 await PaymentHandler.pay(orderData, paymentType); // ✅ 移除双重成功提示 - PaymentHandler会处理成功提示 // Taro.showToast({ // title: '支付成功', // icon: 'success' // }) } catch (error: any) { return navTo('/user/order/order?statusFilter=0', true) // console.error('支付失败:', error) // 只处理PaymentHandler未处理的错误 // if (!error.handled) { // let errorMessage = '支付失败,请重试'; // // // 根据错误类型提供具体提示 // if (error.message?.includes('余额不足')) { // errorMessage = '账户余额不足,请充值后重试'; // } else if (error.message?.includes('优惠券')) { // errorMessage = '优惠券使用失败,请重新选择'; // } else if (error.message?.includes('库存')) { // errorMessage = '商品库存不足,请减少购买数量'; // } else if (error.message?.includes('地址')) { // errorMessage = '收货地址信息有误,请重新选择'; // } else if (error.message) { // errorMessage = error.message; // } // Taro.showToast({ // title: errorMessage, // icon: 'error' // }) // console.log('跳去未付款的订单列表页面') // } } finally { setPayLoading(false) } }; // 统一的数据加载函数 const loadAllData = async () => { try { setLoading(true) setError('') // 分别加载数据,避免类型推断问题 let goodsRes: ShopGoods | null = null if (goodsId) { goodsRes = await getShopGoods(Number(goodsId)) } const [addressRes, paymentRes] = await Promise.all([ listShopUserAddress({isDefault: true}), selectPayment({}) ]) // 设置商品信息 if (goodsRes) { setGoods(goodsRes) } // 设置默认收货地址 if (addressRes && addressRes.length > 0) { setAddress(addressRes[0]) } // 设置支付方式 if (paymentRes && paymentRes.length > 0) { setPayments(paymentRes.map((d) => ({ type: d.type, name: d.name }))) setPayment(paymentRes[0]) } // 加载优惠券(在商品信息加载完成后) if (goodsRes) { await loadUserCoupons() } } catch (err) { console.error('加载数据失败:', err) setError('加载数据失败,请重试') } finally { setLoading(false) } } useDidShow(() => { loadAllData() }) useEffect(() => { loadAllData() }, [goodsId]); // 重新加载数据 const handleRetry = () => { loadAllData() } // 错误状态 if (error) { return ( {error} ) } // 加载状态 if (loading || !goods) { return } return (
{ address && ( Taro.navigateTo({url: '/user/address/index'})}> 送至 {address.province} {address.city} {address.region} {address.address} {address.name} {address.phone} ) } {!address && ( Taro.navigateTo({url: '/user/address/index'})}> 添加收货地址 )} {goods.name} 80g/袋 ¥{goods.price} {goods.stock !== undefined && ( 库存 {goods.stock} 件 )} {payment?.name} )} onClick={() => setIsVisible(true)} /> ¥{getGoodsTotal().toFixed(2)}} /> {selectedCoupon ? `-¥${getCouponDiscount().toFixed(2)}` : '暂未使用'} {(() => { const usableCoupons = filterUsableCoupons(availableCoupons, getGoodsTotal()) if (usableCoupons.length > 0 && !selectedCoupon) { return ( {usableCoupons.length}张可用 ) } else if (usableCoupons.length > 0) { return ( 已选择 ) } else { return } })() } )} onClick={() => setCouponVisible(true)} /> 已优惠 ¥{getCouponDiscount().toFixed(2)} 实付 ¥{getFinalPrice().toFixed(2)} )}/> setOrderRemark(value)} maxLength={100} /> )}/> {/* 支付方式选择 */} setIsVisible(false)} /> {/* 优惠券选择弹窗 */} setCouponVisible(false)} style={{height: '60vh'}} > 选择优惠券 {couponLoading ? ( 加载优惠券中... ) : ( <> {selectedCoupon && ( 当前使用 {selectedCoupon.title} -¥{calculateCouponDiscount(selectedCoupon, getGoodsTotal()).toFixed(2)} )} {(() => { const total = getGoodsTotal() const usableCoupons = filterUsableCoupons(availableCoupons, total) const unusableCoupons = filterUnusableCoupons(availableCoupons, total) return ( <> {unusableCoupons.length > 0 && ( ({ ...coupon, status: 2 as const }))} layout="vertical" showEmpty={false} /> )} ) })()} )}
实付金额: ¥{getFinalPrice().toFixed(2)} {selectedCoupon && ( 已优惠 ¥{getCouponDiscount().toFixed(2)} )}
); }; export default OrderConfirm;