Explorar o código

暂存当前修改以便合并史诗004代码

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
yourname hai 1 mes
pai
achega
c5775217b7

+ 14 - 218
mini/src/pages/payment-success/index.tsx

@@ -4,11 +4,9 @@
  */
 
 import Taro, { useRouter } from '@tarojs/taro'
-import React, { useState, useEffect } from 'react'
 import { View, Text } from '@tarojs/components'
-import { useQuery,useMutation } from '@tanstack/react-query'
-import { orderClient,userClient } from '@/api'
-import { useAuth } from '@/utils/auth'
+import { useQuery } from '@tanstack/react-query'
+import { orderClient } from '@/api'
 import { Navbar } from '@/components/ui/navbar'
 import { Button } from '@/components/ui/button'
 import dayjs from 'dayjs'
@@ -16,215 +14,16 @@ import dayjs from 'dayjs'
 interface PaymentSuccessParams {
   orderId: number
   amount: number
+  paymentMethod?: string // 支付方式:wechat 或 credit
 }
 
 const PaymentSuccessPage = () => {
   // 使用useRouter钩子获取路由参数
   const router = useRouter()
-  const { user } = useAuth()
   const params = router.params
   const orderId = params?.orderId ? parseInt(params.orderId) : 0
   const amount = params?.amount ? parseFloat(params.amount) : 0
-
-
-    
- // 获取当前用户详细信息(包含订阅状态)
- const { data: currentUser } = useQuery({
-  queryKey: ['current-user', user?.id],
-  queryFn: async () => {
-    if (!user?.id) {
-      throw new Error('用户未登录')
-    }
-    const response = await userClient[':id']['$get']({
-      param: { id: user.id }
-    })
-    if (response.status !== 200) {
-      throw new Error('获取用户信息失败')
-    }
-    return response.json()
-  },
-  enabled: !!user?.id,
-})
-
-
-  const handleSubscribe = async () => {
-    try {
-      const subscribed = await requestSubscribeMessage()
-      console.log('订阅结果:', subscribed)
-    } catch (error) {
-      console.error('订阅失败:', error)
-      Taro.showToast({
-        title: '订阅失败,请重试',
-        icon: 'none',
-        duration: 2000
-      })
-    }
-  }
-
-  // 注意:微信小程序订阅消息需要在用户交互时触发
-  // 不能在页面加载时自动调用,所以这里注释掉自动订阅
-  // 改为在用户点击按钮时触发
-
-  // useEffect(() => {
-  //   // 异步引导用户订阅发货通知,不阻塞跳转
-  //   // 只在页面首次加载时执行一次
-  //   const timer = setTimeout(() => {
-  //     requestSubscribeMessage().then(subscribed => {
-  //       console.log('订阅引导完成,用户订阅状态:', subscribed)
-  //     }).catch(error => {
-  //       console.error('订阅引导异常:', error)
-  //     })
-  //   }, 1000) // 延迟1秒执行,让用户先看到支付成功页面
-
-  //   return () => clearTimeout(timer)
-  // }, []) // 空依赖数组,确保只执行一次
-
-  
-   // 检查用户是否已经订阅过发货通知
-   const checkHasSubscribed = (): boolean => {
-    try {
-      // 优先从数据库读取订阅状态
-
-      //console.log("currentUser:",currentUser);
-
-      if (currentUser?.hasSubscribedDeliveryNotice !== undefined) {
-        return currentUser.hasSubscribedDeliveryNotice === true
-      }
-
-      // 如果数据库没有数据,回退到本地存储
-      //const subscribed = Taro.getStorageSync('hasSubscribedDeliveryNotice')
-     // return subscribed === true
-    } catch (error) {
-      console.error('检查订阅状态失败:', error)
-      return false
-    }
-  }
-
-  // 记录用户订阅状态
-  const recordSubscription = (subscribed: boolean) => {
-    try {
-      // 先记录到本地存储
-     // Taro.setStorageSync('hasSubscribedDeliveryNotice', subscribed)
-
-      // 异步更新到数据库
-      if (user?.id && !checkHasSubscribed()) {
-        updateUserSubscriptionMutation.mutate(subscribed, {
-          onSuccess: () => {
-            console.log('用户订阅状态已更新到数据库:', subscribed)
-          },
-          onError: (error) => {
-            console.error('更新用户订阅状态到数据库失败:', error)
-            // 即使数据库更新失败,本地存储仍然有效
-          }
-        })
-      }
-    } catch (error) {
-      console.error('记录订阅状态失败:', error)
-    }
-  }
-
-  // 引导用户订阅发货通知
-  const requestSubscribeMessage = async (): Promise<boolean> => {
-    try {
-      // // 检查用户是否已经订阅过
-      // if (checkHasSubscribed()) {
-      //   console.log('用户已订阅过发货通知,跳过引导')
-      //   return true
-      // }
-
-      // 发货成功通知模板ID
-      const templateId = 'T00N0Wq3ECjksXSvPWUBgOUukl1TCE7PhxqeDnFPfso'
-
-      // 先显示一个友好的提示,让用户知道为什么要订阅
-      const modalRes = await Taro.showModal({
-        title: '订阅发货通知',
-        content: '订阅后,当订单发货时您会收到微信通知,方便您及时了解物流状态。',
-        confirmText: '立即订阅',
-        cancelText: '稍后再说'
-      })
-
-      if (modalRes.confirm) {
-        // 用户点击确认,调用微信订阅消息API
-        try {
-          const result = await Taro.requestSubscribeMessage({
-            tmplIds: [templateId],
-            entityIds:[]
-          })
-
-          console.log('订阅消息结果:', result)
-
-          // 根据用户选择处理结果
-          if (result[templateId] === 'accept') {
-            console.log('用户接受了订阅消息')
-            Taro.showToast({
-              title: '订阅成功,发货时会收到通知',
-              icon: 'success',
-              duration: 2000
-            })
-            // 记录用户已订阅
-            recordSubscription(true)
-            return true
-          } else if (result[templateId] === 'reject') {
-            console.log('用户拒绝了订阅消息')
-            Taro.showToast({
-              title: '已取消订阅',
-              icon: 'none',
-              duration: 1500
-            })
-            // 记录用户拒绝订阅
-            recordSubscription(false)
-            return false
-          } else {
-            console.log('用户未做出选择或发生错误')
-            // 如果用户没有明确选择,不记录状态,下次再询问
-            return false
-          }
-        } catch (error) {
-          console.error('订阅消息失败:', error)
-          // 订阅失败不影响主要流程
-          return false
-        }
-      } else {
-        console.log('用户选择稍后再说')
-        // 用户选择稍后再说,不记录状态,下次再询问
-        return false
-      }
-    } catch (error) {
-      console.error('订阅消息引导失败:', error)
-      // 引导失败不影响主要流程
-      return false
-    }
-  }
-
-  // 更新用户订阅状态
-  const updateUserSubscriptionMutation = useMutation({
-    mutationFn: async (subscribed: boolean) => {
-      if (!user?.id) {
-        throw new Error('用户未登录')
-      }
-
-      const updateData = {
-        hasSubscribedDeliveryNotice: subscribed
-      }
-
-      const response = await userClient[':id']['$put']({
-        param: { id: user.id },
-        json: updateData
-      })
-
-      if (response.status !== 200) {
-        throw new Error('更新用户订阅状态失败')
-      }
-      return response.json()
-    },
-    onError: (error) => {
-      console.error('更新用户订阅状态失败:', error)
-      // 即使更新失败,也不影响主要流程,只在控制台记录错误
-    }
-  })
-
-
-
+  const paymentMethod = params?.paymentMethod || 'wechat' // 默认微信支付
 
   // 检查参数有效性
   const hasValidParams = orderId > 0 && amount > 0
@@ -318,7 +117,9 @@ const PaymentSuccessPage = () => {
         </View>
         <View className="flex justify-between items-center py-3">
           <Text className="text-sm text-gray-600">支付方式:</Text>
-          <Text className="text-sm text-gray-800">微信支付</Text>
+          <Text className="text-sm text-gray-800">
+            {paymentMethod === 'credit' ? '额度支付' : '微信支付'}
+          </Text>
         </View>
       </View>
 
@@ -339,16 +140,13 @@ const PaymentSuccessPage = () => {
         >
           查看订单列表
         </Button>
-
-          <Button
-            onClick={handleSubscribe}
-            className="w-full h-12"
-            variant="outline"
-            size="lg"
-          >
-            📦 订阅发货通知
-          </Button>
-         
+        <Button
+          onClick={handleBackToHome}
+          className="w-full h-12"
+          variant="ghost"
+        >
+          返回首页
+        </Button>
       </View>
 
       {/* 温馨提示 */}
@@ -357,8 +155,6 @@ const PaymentSuccessPage = () => {
         <Text className="text-xs text-gray-600 leading-relaxed whitespace-pre-line">
           • 订单详情可在订单列表中查看
           {'\n'}
-          • 订阅发货通知后,订单发货时会收到微信提醒
-          {'\n'}
           • 如有问题请联系客服
           {'\n'}
           • 感谢您的支持

+ 264 - 103
mini/src/pages/payment/index.tsx

@@ -4,9 +4,9 @@
  */
 
 import Taro, { useRouter } from '@tarojs/taro'
-import { useState } from 'react'
+import { useState, useEffect } from 'react'
 import { View, Text } from '@tarojs/components'
-import { useQuery } from '@tanstack/react-query'
+import { useQuery, useMutation } from '@tanstack/react-query'
 import { Button } from '@/components/ui/button'
 import { Navbar } from '@/components/ui/navbar'
 import {
@@ -16,7 +16,7 @@ import {
   PaymentRateLimiter,
   retryPayment
 } from '@/utils/payment'
-import { paymentClient } from '@/api'
+import { paymentClient, creditBalanceClient } from '@/api'
 
 interface PaymentData {
   timeStamp: string
@@ -26,10 +26,25 @@ interface PaymentData {
   paySign: string
 }
 
+interface CreditBalanceData {
+  totalLimit: number
+  usedAmount: number
+  availableAmount: number
+  isEnabled: boolean
+}
+
+enum PaymentMethod {
+  WECHAT = 'wechat',
+  CREDIT = 'credit'
+}
+
 const PaymentPage = () => {
   const [paymentStatus, setPaymentStatus] = useState<PaymentStatus>(PaymentStatus.PENDING)
   const [isProcessing, setIsProcessing] = useState(false)
   const [errorMessage, setErrorMessage] = useState('')
+  const [selectedPaymentMethod, setSelectedPaymentMethod] = useState<PaymentMethod>(PaymentMethod.WECHAT)
+  const [creditBalance, setCreditBalance] = useState<CreditBalanceData | null>(null)
+  const [isCreditBalanceLoading, setIsCreditBalanceLoading] = useState(false)
 
   // 使用useRouter钩子获取路由参数
   const router = useRouter()
@@ -38,11 +53,59 @@ const PaymentPage = () => {
   const amount = routerParams?.amount ? parseFloat(routerParams.amount) : 0
   const orderNo = routerParams?.orderNo
 
+  // 获取用户额度信息
+  const fetchCreditBalance = async () => {
+    setIsCreditBalanceLoading(true)
+    try {
+      const response = await creditBalanceClient.me.$get({})
+
+      if (response.status === 200) {
+        const balanceData = await response.json()
+        setCreditBalance(balanceData)
+      } else if (response.status === 404) {
+        // 用户没有额度记录,创建默认额度
+        setCreditBalance({
+          totalLimit: 0,
+          usedAmount: 0,
+          availableAmount: 0,
+          isEnabled: false
+        })
+      } else {
+        // 其他错误情况
+        setCreditBalance({
+          totalLimit: 0,
+          usedAmount: 0,
+          availableAmount: 0,
+          isEnabled: false
+        })
+      }
+    } catch (error) {
+      console.error('获取用户额度失败:', error)
+      setCreditBalance({
+        totalLimit: 0,
+        usedAmount: 0,
+        availableAmount: 0,
+        isEnabled: false
+      })
+    } finally {
+      setIsCreditBalanceLoading(false)
+    }
+  }
+
 
-  // 获取支付参数
-  const { data: paymentData, isLoading: paymentLoading } = useQuery({
-    queryKey: ['payment-params', orderId],
-    queryFn: async () => {
+  // 组件加载时获取额度信息
+  useEffect(() => {
+    fetchCreditBalance()
+  }, [])
+
+  // 获取微信支付参数(手动触发)
+  const {
+    mutateAsync: fetchWechatPaymentParams,
+    data: paymentData,
+    isLoading: paymentLoading,
+    error: paymentError
+  } = useMutation({
+    mutationFn: async () => {
       if (!orderId) throw new Error('订单ID无效')
 
       // 调用后端API获取微信支付参数
@@ -70,18 +133,81 @@ const PaymentPage = () => {
       }
 
       return paymentData
-    },
-    enabled: !!orderId && paymentStatus === PaymentStatus.PENDING
+    }
   })
 
-     // 支付状态管理
-     const paymentStateManager = PaymentStateManager.getInstance()
-     const rateLimiter = PaymentRateLimiter.getInstance()
-  
+  // 支付状态管理
+  const paymentStateManager = PaymentStateManager.getInstance()
+  const rateLimiter = PaymentRateLimiter.getInstance()
+
+  // 处理额度支付
+  const handleCreditPayment = async () => {
+    if (!orderId) {
+      setErrorMessage('订单信息不完整')
+      return
+    }
+
+    if (!creditBalance?.isEnabled) {
+      setErrorMessage('额度支付未启用')
+      return
+    }
+
+    // 检查额度是否足够(使用订单金额检查)
+    if (creditBalance.availableAmount < amount) {
+      setErrorMessage(`额度不足,可用额度: ¥${creditBalance.availableAmount.toFixed(2)}`)
+      return
+    }
+
+    setIsProcessing(true)
+    setErrorMessage('')
+    setPaymentStatus(PaymentStatus.PROCESSING)
+
+    try {
+      const response = await creditBalanceClient.payment.$post({
+        json: {
+          referenceId: orderId.toString(), // 传递订单ID而不是订单号
+          remark: `订单支付 - ${orderNo || `ORD${orderId}`}`
+        }
+      })
+
+      if (response.status === 200) {
+        // 额度支付成功
+        setPaymentStatus(PaymentStatus.SUCCESS)
+
+        // 更新本地额度信息
+        const updatedBalance = await response.json()
+        setCreditBalance(updatedBalance)
+
+        // 跳转到支付成功页面
+        setTimeout(() => {
+          Taro.redirectTo({
+            url: `/pages/payment-success/index?orderId=${orderId}&amount=${amount}&paymentMethod=credit`
+          })
+        }, 1500)
+      } else {
+        const errorData = await response.json()
+        setPaymentStatus(PaymentStatus.FAILED)
+        setErrorMessage(errorData.message || '额度支付失败')
+      }
+    } catch (error: any) {
+      console.error('额度支付处理异常:', error)
+      setPaymentStatus(PaymentStatus.FAILED)
+      setErrorMessage(error.message || '额度支付异常')
+    } finally {
+      setIsProcessing(false)
+    }
+  }
+
   // 处理支付
   const handlePayment = async () => {
-    if (!paymentData || !orderId) {
-      setErrorMessage('支付参数不完整')
+    if (selectedPaymentMethod === PaymentMethod.CREDIT) {
+      await handleCreditPayment()
+      return
+    }
+
+    // 微信支付逻辑
+    if (!orderId) {
+      setErrorMessage('订单信息不完整')
       return
     }
 
@@ -95,83 +221,44 @@ const PaymentPage = () => {
     setIsProcessing(true)
     setErrorMessage('')
     setPaymentStatus(PaymentStatus.PROCESSING)
-    paymentStateManager.setPaymentState(orderId, PaymentStatus.PROCESSING)
 
     try {
+      // 先获取微信支付参数
+      const wechatPaymentData = await fetchWechatPaymentParams()
+
+      if (!wechatPaymentData) {
+        setPaymentStatus(PaymentStatus.FAILED)
+        setErrorMessage('获取支付参数失败')
+        return
+      }
+
+      paymentStateManager.setPaymentState(orderId, PaymentStatus.PROCESSING)
+
       // 记录支付尝试
       rateLimiter.recordAttempt(orderId)
 
       // 调用微信支付
-      // console.debug('开始调用微信支付...')
-      const paymentResult = await requestWechatPayment(paymentData)
-      // console.debug('微信支付调用完成,结果:', paymentResult)
+      const paymentResult = await requestWechatPayment(wechatPaymentData)
 
       if (paymentResult.success) {
         // 支付成功
-       // console.debug('微信支付成功,开始更新状态')
-       // console.debug('更新前支付状态:', paymentStatus)
-
-        // 使用函数式更新确保状态正确设置
-        setPaymentStatus(prevStatus => {
-          console.debug('setPaymentStatus 被调用,前一个状态:', prevStatus, '新状态:', PaymentStatus.SUCCESS)
-          return PaymentStatus.SUCCESS
-        })
-
+        setPaymentStatus(PaymentStatus.SUCCESS)
         paymentStateManager.setPaymentState(orderId, PaymentStatus.SUCCESS)
 
         // 清除频率限制记录
         rateLimiter.clearAttempts(orderId)
 
-
-        // 调用后端API更新订单支付状态为已支付 (2)
-        try {
-         // console.debug('开始调用后端API更新订单支付状态')
-          await paymentClient.payment.updateOrderStatus.$post({
-            json: {
-              orderId: orderId,
-              payState: 2 // 2表示支付成功
-            }
-          })
-         // console.debug('数据库订单支付状态更新成功')
-        } catch (error) {
-          console.error('数据库更新订单支付状态失败:', error)
-          // 这里不抛出错误,因为支付已经成功,只是状态同步失败
-          // 可以记录日志或发送通知给管理员
-        }
-
-       // console.debug('支付状态已更新为SUCCESS,准备跳转')
-
-        // 强制触发UI重新渲染
-        setTimeout(() => {
-         // console.debug('强制触发UI更新')
-          setPaymentStatus(currentStatus => currentStatus)
-        }, 100)
-
-        // 确保状态更新显示在UI上
+        // 跳转到支付成功页面
         setTimeout(() => {
-         // console.debug('开始跳转到支付成功页面')
           Taro.redirectTo({
-            url: `/pages/payment-success/index?orderId=${orderId}&amount=${amount}`
+            url: `/pages/payment-success/index?orderId=${orderId}&amount=${amount}&paymentMethod=wechat`
           })
-        }, 2000) // 增加延迟时间,确保用户看到成功状态
+        }, 1500)
       } else {
         // 支付失败
-        console.debug('支付失败,开始更新状态')
-        console.debug('更新前支付状态:', paymentStatus)
-
-        setPaymentStatus(prevStatus => {
-          console.debug('setPaymentStatus 被调用,前一个状态:', prevStatus, '新状态:', PaymentStatus.FAILED)
-          return PaymentStatus.FAILED
-        })
+        setPaymentStatus(PaymentStatus.FAILED)
         paymentStateManager.setPaymentState(orderId, PaymentStatus.FAILED)
 
-        await paymentClient.payment.updateOrderStatus.$post({
-          json: {
-            orderId: orderId,
-            payState: 3 // 3表示支付失败
-          }
-        })
-
         if (paymentResult.type === 'cancel') {
           setErrorMessage('用户取消支付')
         } else {
@@ -190,47 +277,42 @@ const PaymentPage = () => {
 
   // 重试支付
   const handleRetryPayment = async () => {
-    if (!paymentData || !orderId) return
+    if (selectedPaymentMethod === PaymentMethod.CREDIT) {
+      await handleCreditPayment()
+      return
+    }
+
+    if (!orderId) return
 
     setIsProcessing(true)
     setErrorMessage('')
 
     try {
+      // 先获取微信支付参数
+      const wechatPaymentData = await fetchWechatPaymentParams()
+
+      if (!wechatPaymentData) {
+        setPaymentStatus(PaymentStatus.FAILED)
+        setErrorMessage('获取支付参数失败')
+        return
+      }
+
       const retryResult = await retryPayment(
-        () => requestWechatPayment(paymentData),
+        () => requestWechatPayment(wechatPaymentData),
         3,
         1000
       )
 
       if (retryResult.success) {
-        console.debug('重试支付成功,开始更新状态')
         setPaymentStatus(PaymentStatus.SUCCESS)
         paymentStateManager.setPaymentState(orderId, PaymentStatus.SUCCESS)
 
-        // 调用后端API更新订单支付状态为已支付 (2)
-        try {
-          console.debug('开始调用后端API更新订单支付状态(重试)')
-          await paymentClient.payment.updateOrderStatus.$post({
-            json: {
-              orderId: orderId,
-              payState: 2 // 2表示支付成功
-            }
-          })
-          console.debug('订单支付状态更新成功(重试)')
-        } catch (error) {
-          console.error('更新订单支付状态失败(重试):', error)
-          // 这里不抛出错误,因为支付已经成功,只是状态同步失败
-        }
-
-        console.debug('重试支付状态已更新为SUCCESS,准备跳转')
-
         // 跳转到支付成功页面
         setTimeout(() => {
-          console.debug('开始跳转到支付成功页面(重试)')
           Taro.redirectTo({
-            url: `/pages/payment-success/index?orderId=${orderId}&amount=${amount}`
+            url: `/pages/payment-success/index?orderId=${orderId}&amount=${amount}&paymentMethod=wechat`
           })
-        }, 2000) // 增加延迟时间,确保用户看到成功状态
+        }, 1500)
       } else {
         setPaymentStatus(PaymentStatus.FAILED)
         setErrorMessage(retryResult.message || '支付重试失败')
@@ -314,19 +396,89 @@ const PaymentPage = () => {
       <View className="p-5">
         {/* 头部 */}
         <View className="text-center py-6 bg-white rounded-2xl mb-5">
-          <Text className="text-2xl font-bold text-gray-800">支付订单</Text>
+          <Text className="text-2xl font-bold text-gray-800" data-testid="payment-page-title">支付订单</Text>
         </View>
 
       {/* 订单信息 */}
-      <View className="bg-white rounded-2xl p-6 mb-5">
+      <View className="bg-white rounded-2xl p-6 mb-5" data-testid="order-info">
         <View className="flex justify-between items-center mb-4">
           <Text className="text-sm text-gray-600">订单号:</Text>
-          <Text className="text-sm text-gray-800">{orderNo || `ORD${orderId}`}</Text>
+          <Text className="text-sm text-gray-800" data-testid="order-no">{orderNo || `ORD${orderId}`}</Text>
         </View>
         <View className="flex justify-between items-center">
           <Text className="text-sm text-gray-600">支付金额:</Text>
-          <Text className="text-2xl font-bold text-orange-500">¥{amount.toFixed(2)}</Text>
+          <Text className="text-2xl font-bold text-orange-500" data-testid="payment-amount">¥{amount.toFixed(2)}</Text>
+        </View>
+      </View>
+
+      {/* 支付方式选择 */}
+      <View className="bg-white rounded-2xl p-6 mb-5">
+        <Text className="text-sm font-bold text-gray-800 block mb-4">选择支付方式</Text>
+
+        {/* 微信支付选项 */}
+        <View
+          className={`flex items-center justify-between p-4 mb-3 rounded-xl border-2 ${
+            selectedPaymentMethod === PaymentMethod.WECHAT
+              ? 'border-blue-500 bg-blue-50'
+              : 'border-gray-200'
+          }`}
+          onClick={() => setSelectedPaymentMethod(PaymentMethod.WECHAT)}
+          data-testid="wechat-payment-option"
+        >
+          <View className="flex items-center">
+            <View className="w-10 h-10 bg-green-100 rounded-full flex items-center justify-center mr-3">
+              <Text className="text-green-600 text-lg">💰</Text>
+            </View>
+            <View>
+              <Text className="text-sm font-bold text-gray-800">微信支付</Text>
+              <Text className="text-xs text-gray-500">使用微信支付完成付款</Text>
+            </View>
+          </View>
+          {selectedPaymentMethod === PaymentMethod.WECHAT && (
+            <View className="w-6 h-6 bg-blue-500 rounded-full flex items-center justify-center" data-testid="wechat-selected">
+              <Text className="text-white text-xs">✓</Text>
+            </View>
+          )}
         </View>
+
+        {/* 额度支付选项 - 只在额度满足时才显示 */}
+        {creditBalance?.isEnabled && creditBalance?.availableAmount >= amount && (
+          <View
+            className={`flex items-center justify-between p-4 rounded-xl border-2 ${
+              selectedPaymentMethod === PaymentMethod.CREDIT
+                ? 'border-blue-500 bg-blue-50'
+                : 'border-gray-200'
+            }`}
+            onClick={() => setSelectedPaymentMethod(PaymentMethod.CREDIT)}
+            data-testid="credit-payment-option"
+          >
+            <View className="flex items-center">
+              <View className="w-10 h-10 bg-purple-100 rounded-full flex items-center justify-center mr-3">
+                <Text className="text-purple-600 text-lg">💳</Text>
+              </View>
+              <View>
+                <Text className="text-sm font-bold text-gray-800">额度支付</Text>
+                <Text className="text-xs text-gray-500" data-testid="available-amount-text">
+                  使用信用额度支付
+                </Text>
+              </View>
+            </View>
+            {selectedPaymentMethod === PaymentMethod.CREDIT && (
+              <View className="w-6 h-6 bg-blue-500 rounded-full flex items-center justify-center" data-testid="credit-selected">
+                <Text className="text-white text-xs">✓</Text>
+              </View>
+            )}
+          </View>
+        )}
+
+        {/* 额度支付说明 */}
+        {selectedPaymentMethod === PaymentMethod.CREDIT && creditBalance && (
+          <View className="mt-4 p-3 bg-blue-50 rounded-lg" data-testid="credit-payment-details">
+            <Text className="text-xs text-blue-700">
+              • 使用信用额度支付,无需立即付款
+            </Text>
+          </View>
+        )}
       </View>
 
       {/* 支付状态 */}
@@ -339,12 +491,21 @@ const PaymentPage = () => {
         {paymentStatus === PaymentStatus.PENDING && (
           <Button
             onClick={handlePayment}
-            disabled={isProcessing || paymentLoading}
-            className={`w-full h-22 bg-gradient-to-r from-orange-500 to-orange-400 text-white rounded-full text-lg font-bold ${
+            disabled={isProcessing || paymentLoading || (selectedPaymentMethod === PaymentMethod.CREDIT && (!creditBalance?.isEnabled || creditBalance?.availableAmount < amount))}
+            className={`w-full h-22 ${
+              selectedPaymentMethod === PaymentMethod.CREDIT
+                ? 'bg-gradient-to-r from-purple-500 to-purple-400'
+                : 'bg-gradient-to-r from-orange-500 to-orange-400'
+            } text-white rounded-full text-lg font-bold ${
               isProcessing ? 'bg-gray-400' : ''
             }`}
+            data-testid="pay-button"
           >
-            {isProcessing ? '支付中...' : `确认支付 ¥${amount.toFixed(2)}`}
+            {isProcessing ? '支付中...' :
+              selectedPaymentMethod === PaymentMethod.CREDIT
+                ? `额度支付 ¥${amount.toFixed(2)}`
+                : `微信支付 ¥${amount.toFixed(2)}`
+            }
           </Button>
         )}
 
@@ -366,8 +527,8 @@ const PaymentPage = () => {
         )}
 
         {paymentStatus === PaymentStatus.SUCCESS && (
-          <Button disabled className="w-full h-22 bg-green-500 text-white rounded-full text-lg font-bold">
-            支付成功
+          <Button disabled className="w-full h-22 bg-gray-100 text-gray-500 rounded-full text-sm">
+            支付成功
           </Button>
         )}
       </View>

+ 464 - 0
mini/tests/integration/credit-payment-flow.test.tsx

@@ -0,0 +1,464 @@
+/**
+ * 额度支付流程集成测试
+ * 测试完整额度支付流程
+ */
+
+import { render, screen, waitFor, fireEvent } from '@testing-library/react'
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
+import PaymentPage from '@/pages/payment/index'
+import { creditBalanceClient, paymentClient } from '@/api'
+import { mockUseRouter, mockNavigateTo, mockShowToast, mockRedirectTo } from '~/__mocks__/taroMock'
+
+// @tarojs/taro 已经在 jest.config.js 中通过 moduleNameMapper 重定向到 mock 文件
+// 不需要额外 mock
+
+// Mock API客户端
+jest.mock('@/api', () => ({
+  creditBalanceClient: {
+    me: {
+      $get: jest.fn(),
+    },
+    payment: {
+      $post: jest.fn(),
+    },
+  },
+  paymentClient: {
+    payment: {
+      $post: jest.fn(),
+    },
+  },
+}))
+
+// Mock 支付工具函数
+jest.mock('@/utils/payment', () => ({
+  requestWechatPayment: jest.fn(),
+  PaymentStatus: {
+    PENDING: 'pending',
+    PROCESSING: 'processing',
+    SUCCESS: 'success',
+    FAILED: 'failed',
+  },
+  PaymentStateManager: {
+    getInstance: jest.fn(() => ({
+      setPaymentState: jest.fn(),
+      clearPaymentState: jest.fn(),
+    })),
+  },
+  PaymentRateLimiter: {
+    getInstance: jest.fn(() => ({
+      isRateLimited: jest.fn(() => ({ limited: false })),
+      recordAttempt: jest.fn(),
+      clearAttempts: jest.fn(),
+    })),
+  },
+  retryPayment: jest.fn(),
+}))
+
+// 创建测试QueryClient
+const createTestQueryClient = () => new QueryClient({
+  defaultOptions: {
+    queries: { retry: false },
+    mutations: { retry: false },
+  },
+})
+
+// 测试包装器
+const TestWrapper = ({ children }: { children: React.ReactNode }) => (
+  <QueryClientProvider client={createTestQueryClient()}>
+    {children}
+  </QueryClientProvider>
+)
+
+// 测试数据工厂
+const createTestCreditBalance = (overrides = {}) => ({
+  totalLimit: 1000,
+  usedAmount: 200,
+  availableAmount: 800,
+  isEnabled: true,
+  ...overrides,
+})
+
+const createTestPaymentData = () => ({
+  timeStamp: '1234567890',
+  nonceStr: 'test-nonce',
+  package: 'prepay_id=test_prepay_id',
+  signType: 'MD5',
+  paySign: 'test-sign',
+})
+
+describe('额度支付流程集成测试', () => {
+  beforeEach(() => {
+    jest.clearAllMocks()
+    jest.useFakeTimers()
+
+    // 设置默认路由参数
+    mockUseRouter.mockReturnValue({
+      params: {
+        orderId: '123',
+        amount: '100',
+        orderNo: 'ORD123456',
+      },
+    })
+  })
+
+  afterEach(() => {
+    jest.useRealTimers()
+  })
+
+  test('完整额度支付流程:从选择到支付成功', async () => {
+    // Mock 额度查询返回正常数据
+    const initialBalance = createTestCreditBalance({ availableAmount: 800 })
+    ;(creditBalanceClient.me.$get as jest.Mock).mockResolvedValue({
+      status: 200,
+      json: () => Promise.resolve(initialBalance),
+    })
+
+    // Mock 额度支付成功
+    const updatedBalance = createTestCreditBalance({
+      usedAmount: 300,  // 原200 + 支付100 = 300
+      availableAmount: 700  // 原800 - 支付100 = 700
+    })
+    ;(creditBalanceClient.payment.$post as jest.Mock).mockResolvedValue({
+      status: 200,
+      json: () => Promise.resolve(updatedBalance),
+    })
+
+    render(
+      <TestWrapper>
+        <PaymentPage />
+      </TestWrapper>
+    )
+
+    // 1. 验证页面加载和额度显示
+    await waitFor(() => {
+      expect(screen.getByTestId('payment-page-title')).toBeInTheDocument()
+      expect(screen.getByTestId('available-amount-text')).toHaveTextContent(/使用信用额度支付/)
+    })
+
+    // 2. 选择额度支付方式
+    const creditOption = screen.getByTestId('credit-payment-option')
+    fireEvent.click(creditOption)
+
+    await waitFor(() => {
+      expect(creditOption).toHaveClass('border-blue-500')
+      expect(screen.getByTestId('pay-button')).toHaveTextContent('额度支付 ¥100.00')
+    })
+
+    // 3. 验证额度详情显示
+    const creditDetails = screen.getByTestId('credit-payment-details')
+    expect(creditDetails).toHaveTextContent(/使用信用额度支付,无需立即付款/)
+    // 不应该包含额度详情
+    expect(creditDetails).not.toHaveTextContent(/可用额度:/)
+    expect(creditDetails).not.toHaveTextContent(/总额度:/)
+    expect(creditDetails).not.toHaveTextContent(/已用额度:/)
+
+    // 4. 点击支付按钮
+    const payButton = screen.getByTestId('pay-button')
+    fireEvent.click(payButton)
+
+    // 5. 验证支付处理中状态
+    await waitFor(() => {
+      expect(screen.getByText('支付中...')).toBeInTheDocument()
+    })
+
+    // 6. 验证调用了额度支付API
+    await waitFor(() => {
+      expect(creditBalanceClient.payment.$post).toHaveBeenCalledWith({
+        json: {
+          referenceId: '123', // 传递订单ID而不是订单号
+          remark: '订单支付 - ORD123456',
+        },
+      })
+    })
+
+    // 7. 验证支付成功状态
+    await waitFor(() => {
+      // 使用类名选择器找到支付成功状态文本
+      expect(screen.getByText('支付成功', { selector: 'span.text-xl' })).toBeInTheDocument()
+    })
+
+    // 8. 推进时间以触发跳转
+    jest.advanceTimersByTime(1600)
+
+    // 9. 验证跳转到成功页面
+    await waitFor(() => {
+      expect(mockRedirectTo).toHaveBeenCalledWith({
+        url: '/pages/payment-success/index?orderId=123&amount=100&paymentMethod=credit',
+      })
+    })
+  })
+
+  test('额度支付失败流程:显示错误并可以重试', async () => {
+    // Mock 额度查询返回正常数据
+    const initialBalance = createTestCreditBalance({ availableAmount: 800 })
+    ;(creditBalanceClient.me.$get as jest.Mock).mockResolvedValue({
+      status: 200,
+      json: () => Promise.resolve(initialBalance),
+    })
+
+    // Mock 额度支付第一次失败,第二次成功
+    let paymentCallCount = 0
+    ;(creditBalanceClient.payment.$post as jest.Mock).mockImplementation(() => {
+      paymentCallCount++
+      if (paymentCallCount === 1) {
+        return Promise.resolve({
+          status: 400,
+          json: () => Promise.resolve({ message: '额度支付失败,请重试' }),
+        })
+      } else {
+        return Promise.resolve({
+          status: 200,
+          json: () => Promise.resolve(createTestCreditBalance({ usedAmount: 300, availableAmount: 700 })),
+        })
+      }
+    })
+
+    render(
+      <TestWrapper>
+        <PaymentPage />
+      </TestWrapper>
+    )
+
+    // 等待页面加载
+    await waitFor(() => {
+      expect(screen.getByTestId('available-amount-text')).toHaveTextContent(/使用信用额度支付/)
+    })
+
+    // 选择额度支付
+    const creditOption = screen.getByTestId('credit-payment-option')
+    fireEvent.click(creditOption)
+
+    // 点击支付按钮(第一次失败)
+    const payButton = screen.getByTestId('pay-button')
+    fireEvent.click(payButton)
+
+    // 验证显示错误信息
+    await waitFor(() => {
+      expect(screen.getByText('额度支付失败,请重试')).toBeInTheDocument()
+      expect(screen.getByText('重试支付')).toBeInTheDocument()
+    })
+
+    // 点击重试按钮
+    const retryButton = screen.getByText('重试支付')
+    fireEvent.click(retryButton)
+
+    // 验证第二次支付成功
+    await waitFor(() => {
+      // 使用更精确的选择器,避免多个"支付成功"元素
+      expect(screen.getByText('支付成功', { selector: 'span.text-xl' })).toBeInTheDocument()
+    })
+
+    // 推进时间以触发跳转
+    jest.advanceTimersByTime(1600)
+
+    await waitFor(() => {
+      expect(mockRedirectTo).toHaveBeenCalledWith({
+        url: '/pages/payment-success/index?orderId=123&amount=100&paymentMethod=credit',
+      })
+    })
+  })
+
+  test('额度不足时的支付流程', async () => {
+    // Mock 额度查询返回额度不足的数据
+    const initialBalance = createTestCreditBalance({
+      totalLimit: 50,
+      usedAmount: 45,
+      availableAmount: 5  // 可用额度5元,支付金额100元
+    })
+    ;(creditBalanceClient.me.$get as jest.Mock).mockResolvedValue({
+      status: 200,
+      json: () => Promise.resolve(initialBalance),
+    })
+
+    render(
+      <TestWrapper>
+        <PaymentPage />
+      </TestWrapper>
+    )
+
+    // 等待页面加载
+    await waitFor(() => {
+      // 额度不足时,额度支付选项不应该显示
+      expect(screen.queryByTestId('credit-payment-option')).not.toBeInTheDocument()
+    })
+
+    // 验证支付按钮没有被禁用(因为默认选择微信支付)
+    const payButton = screen.getByTestId('pay-button')
+    expect(payButton).not.toBeDisabled()
+  })
+
+  test('额度为0时的支付流程', async () => {
+    // Mock 额度查询返回额度为0的数据
+    const initialBalance = createTestCreditBalance({
+      totalLimit: 0,
+      usedAmount: 0,
+      availableAmount: 0,
+      isEnabled: false,
+    })
+    ;(creditBalanceClient.me.$get as jest.Mock).mockResolvedValue({
+      status: 200,
+      json: () => Promise.resolve(initialBalance),
+    })
+
+    render(
+      <TestWrapper>
+        <PaymentPage />
+      </TestWrapper>
+    )
+
+    // 等待页面加载
+    await waitFor(() => {
+      expect(screen.getByTestId('payment-page-title')).toBeInTheDocument()
+    })
+
+    // 验证额度支付选项不显示(因为额度为0且未启用)
+    expect(screen.queryByTestId('credit-payment-option')).not.toBeInTheDocument()
+
+    // 验证支付按钮没有被禁用(因为默认选择微信支付)
+    const payButton = screen.getByTestId('pay-button')
+    expect(payButton).not.toBeDisabled()
+  })
+
+  test('额度支付与微信支付切换流程', async () => {
+    // Mock 额度查询返回正常数据
+    const initialBalance = createTestCreditBalance({ availableAmount: 800 })
+    ;(creditBalanceClient.me.$get as jest.Mock).mockResolvedValue({
+      status: 200,
+      json: () => Promise.resolve(initialBalance),
+    })
+
+    // Mock 微信支付参数
+    const mockPaymentData = createTestPaymentData()
+    ;(paymentClient.payment.$post as jest.Mock).mockResolvedValue({
+      status: 200,
+      json: () => Promise.resolve(mockPaymentData),
+    })
+
+    render(
+      <TestWrapper>
+        <PaymentPage />
+      </TestWrapper>
+    )
+
+    // 等待页面加载
+    await waitFor(() => {
+      expect(screen.getByTestId('available-amount-text')).toHaveTextContent(/使用信用额度支付/)
+    })
+
+    // 初始为微信支付选中
+    const wechatOption = screen.getByTestId('wechat-payment-option')
+    expect(wechatOption).toHaveClass('border-blue-500')
+    expect(screen.getByText('微信支付 ¥100.00')).toBeInTheDocument()
+
+    // 切换到额度支付
+    const creditOption = screen.getByTestId('credit-payment-option')
+    fireEvent.click(creditOption)
+
+    await waitFor(() => {
+      expect(creditOption).toHaveClass('border-blue-500')
+      expect(screen.getByTestId('pay-button')).toHaveTextContent('额度支付 ¥100.00')
+    })
+
+    // 验证额度详情显示
+    expect(screen.getByText('• 使用信用额度支付,无需立即付款')).toBeInTheDocument()
+
+    // 切换回微信支付
+    fireEvent.click(wechatOption)
+
+    await waitFor(() => {
+      expect(wechatOption).toHaveClass('border-blue-500')
+      expect(screen.getByTestId('pay-button')).toHaveTextContent('微信支付 ¥100.00')
+    })
+
+    // 验证额度详情隐藏
+    expect(screen.queryByText('• 使用信用额度支付,无需立即付款')).not.toBeInTheDocument()
+  })
+
+  test('网络异常时的降级处理', async () => {
+    // Mock 额度查询网络异常
+    ;(creditBalanceClient.me.$get as jest.Mock).mockRejectedValue(new Error('网络连接失败'))
+
+    render(
+      <TestWrapper>
+        <PaymentPage />
+      </TestWrapper>
+    )
+
+    // 等待页面加载(即使额度查询失败,页面也应该显示)
+    await waitFor(() => {
+      // 使用data-testid查询支付页面标题
+      expect(screen.getByTestId('payment-page-title')).toBeInTheDocument()
+    })
+
+    // 验证额度支付选项不显示(因为查询失败)
+    // 当额度查询失败时,额度支付选项不应该显示
+    expect(screen.queryByTestId('credit-payment-option')).not.toBeInTheDocument()
+
+    // 验证支付按钮没有被禁用(因为默认选择微信支付,额度查询失败不影响微信支付)
+    const payButton = screen.getByTestId('pay-button')
+    expect(payButton).not.toBeDisabled()
+  })
+
+  test('支付过程中的取消操作', async () => {
+    // Mock 额度查询返回正常数据
+    const initialBalance = createTestCreditBalance({ availableAmount: 800 })
+    ;(creditBalanceClient.me.$get as jest.Mock).mockResolvedValue({
+      status: 200,
+      json: () => Promise.resolve(initialBalance),
+    })
+
+    // Mock 额度支付延迟(模拟用户取消)
+    let resolvePayment: any
+    const paymentPromise = new Promise((resolve) => {
+      resolvePayment = resolve
+    })
+    ;(creditBalanceClient.payment.$post as jest.Mock).mockReturnValue(paymentPromise)
+
+    render(
+      <TestWrapper>
+        <PaymentPage />
+      </TestWrapper>
+    )
+
+    // 等待页面加载
+    await waitFor(() => {
+      expect(screen.getByTestId('available-amount-text')).toHaveTextContent(/使用信用额度支付/)
+    })
+
+    // 选择额度支付
+    const creditOption = screen.getByTestId('credit-payment-option')
+    fireEvent.click(creditOption)
+
+    // 点击支付按钮
+    const payButton = screen.getByTestId('pay-button')
+    fireEvent.click(payButton)
+
+    // 验证支付处理中状态
+    await waitFor(() => {
+      expect(screen.getByText('支付中...')).toBeInTheDocument()
+    })
+
+    // 此时页面应该显示支付处理中,用户无法进行其他操作
+    await waitFor(() => {
+      // 重新获取按钮元素(文本已变为"支付处理中...")
+      const processingButton = screen.getByText('支付处理中...')
+      // 检查按钮是否被禁用
+      expect(processingButton).toBeDisabled()
+    })
+
+    // 模拟支付完成(超时或其他原因)
+    resolvePayment({
+      status: 200,
+      json: () => Promise.resolve(createTestCreditBalance({ usedAmount: 300, availableAmount: 700 })),
+    })
+
+    // 验证支付成功
+    await waitFor(() => {
+      // 使用更具体的查询,避免多个"支付成功"元素
+      expect(screen.getByText('支付成功', { selector: 'span.text-xl' })).toBeInTheDocument()
+    })
+
+    // 推进时间以触发跳转
+    jest.advanceTimersByTime(1600)
+  })
+})

+ 528 - 0
mini/tests/unit/pages/payment/credit-payment.test.tsx

@@ -0,0 +1,528 @@
+/**
+ * 支付页面额度支付单元测试
+ * 测试额度支付选项功能
+ */
+
+import { render, screen, waitFor, fireEvent } from '@testing-library/react'
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
+import PaymentPage from '@/pages/payment/index'
+import { creditBalanceClient } from '@/api'
+import { mockUseRouter, mockRedirectTo, mockShowToast } from '~/__mocks__/taroMock'
+
+// @tarojs/taro 已经在 jest.config.js 中通过 moduleNameMapper 重定向到 mock 文件
+// 不需要额外 mock
+
+// Mock API客户端
+jest.mock('@/api', () => ({
+  creditBalanceClient: {
+    me: {
+      $get: jest.fn(),
+    },
+    payment: {
+      $post: jest.fn(),
+    },
+  },
+  paymentClient: {
+    payment: {
+      $post: jest.fn(),
+    },
+  },
+}))
+
+// Mock 支付工具函数
+jest.mock('@/utils/payment', () => ({
+  requestWechatPayment: jest.fn(),
+  PaymentStatus: {
+    PENDING: 'pending',
+    PROCESSING: 'processing',
+    SUCCESS: 'success',
+    FAILED: 'failed',
+  },
+  PaymentStateManager: {
+    getInstance: jest.fn(() => ({
+      setPaymentState: jest.fn(),
+      clearPaymentState: jest.fn(),
+    })),
+  },
+  PaymentRateLimiter: {
+    getInstance: jest.fn(() => ({
+      isRateLimited: jest.fn(() => ({ limited: false })),
+      recordAttempt: jest.fn(),
+      clearAttempts: jest.fn(),
+    })),
+  },
+  retryPayment: jest.fn(),
+}))
+
+// 创建测试QueryClient
+const createTestQueryClient = () => new QueryClient({
+  defaultOptions: {
+    queries: { retry: false },
+    mutations: { retry: false },
+  },
+})
+
+// 测试包装器
+const TestWrapper = ({ children }: { children: React.ReactNode }) => (
+  <QueryClientProvider client={createTestQueryClient()}>
+    {children}
+  </QueryClientProvider>
+)
+
+// 测试数据工厂
+const createTestCreditBalance = (overrides = {}) => ({
+  totalLimit: 1000,
+  usedAmount: 200,
+  availableAmount: 800,
+  isEnabled: true,
+  ...overrides,
+})
+
+const createTestPaymentData = () => ({
+  timeStamp: '1234567890',
+  nonceStr: 'test-nonce',
+  package: 'prepay_id=test_prepay_id',
+  signType: 'MD5',
+  paySign: 'test-sign',
+})
+
+describe('支付页面额度支付功能测试', () => {
+  beforeEach(() => {
+    jest.clearAllMocks()
+    jest.useFakeTimers()
+
+    // 设置默认路由参数
+    mockUseRouter.mockReturnValue({
+      params: {
+        orderId: '123',
+        amount: '100',
+        orderNo: 'ORD123456',
+      },
+    })
+  })
+
+  afterEach(() => {
+    jest.useRealTimers()
+  })
+
+  test('应该正确渲染支付页面', async () => {
+    // Mock 额度查询返回正常数据
+    const mockCreditBalance = createTestCreditBalance()
+    ;(creditBalanceClient.me.$get as jest.Mock).mockResolvedValue({
+      status: 200,
+      json: () => Promise.resolve(mockCreditBalance),
+    })
+
+    render(
+      <TestWrapper>
+        <PaymentPage />
+      </TestWrapper>
+    )
+
+    // 验证页面标题
+    await waitFor(() => {
+      expect(screen.getByTestId('payment-page-title')).toBeInTheDocument()
+    })
+
+    // 验证订单信息显示
+    expect(screen.getByTestId('order-info')).toBeInTheDocument()
+    expect(screen.getByTestId('order-no')).toHaveTextContent('ORD123456')
+    expect(screen.getByTestId('payment-amount')).toHaveTextContent('¥100.00')
+
+    // 等待额度加载完成
+    await waitFor(() => {
+      expect(screen.getByTestId('available-amount-text')).toHaveTextContent('使用信用额度支付')
+    })
+
+    // 验证支付方式选项
+    expect(screen.getByTestId('wechat-payment-option')).toBeInTheDocument()
+    expect(screen.getByTestId('credit-payment-option')).toBeInTheDocument()
+  })
+
+  test('应该显示额度支付选项(只在额度满足时)', async () => {
+    // Mock 额度查询返回正常数据(额度足够)
+    const mockCreditBalance = createTestCreditBalance({ availableAmount: 800 })
+    ;(creditBalanceClient.me.$get as jest.Mock).mockResolvedValue({
+      status: 200,
+      json: () => Promise.resolve(mockCreditBalance),
+    })
+
+    render(
+      <TestWrapper>
+        <PaymentPage />
+      </TestWrapper>
+    )
+
+    // 等待额度加载完成
+    await waitFor(() => {
+      expect(screen.getByTestId('available-amount-text')).toHaveTextContent('使用信用额度支付')
+    })
+
+    // 验证额度支付选项显示
+    const creditPaymentOption = screen.getByTestId('credit-payment-option')
+    expect(creditPaymentOption).toBeInTheDocument()
+    expect(creditPaymentOption).not.toHaveClass('opacity-50')
+  })
+
+  test('额度为0时不应该显示额度支付选项', async () => {
+    // Mock 额度查询返回额度为0的数据
+    const mockCreditBalance = createTestCreditBalance({
+      totalLimit: 0,
+      usedAmount: 0,
+      availableAmount: 0,
+      isEnabled: false,
+    })
+    ;(creditBalanceClient.me.$get as jest.Mock).mockResolvedValue({
+      status: 200,
+      json: () => Promise.resolve(mockCreditBalance),
+    })
+
+    render(
+      <TestWrapper>
+        <PaymentPage />
+      </TestWrapper>
+    )
+
+    // 等待额度加载完成 - 现在额度未启用时,额度支付选项根本不显示
+    // 所以没有特定的文本需要等待
+    await waitFor(() => {
+      // 验证只有微信支付选项显示
+      expect(screen.getByTestId('wechat-payment-option')).toBeInTheDocument()
+    })
+
+    // 验证额度支付选项不显示
+    expect(screen.queryByTestId('credit-payment-option')).not.toBeInTheDocument()
+    expect(screen.queryByTestId('credit-disabled-text')).not.toBeInTheDocument()
+  })
+
+  test('额度不足时不应该显示额度支付选项', async () => {
+    // Mock 额度查询返回额度不足的数据
+    const mockCreditBalance = createTestCreditBalance({
+      totalLimit: 50,
+      usedAmount: 40,
+      availableAmount: 10, // 可用额度10元,支付金额100元
+      isEnabled: true,
+    })
+    ;(creditBalanceClient.me.$get as jest.Mock).mockResolvedValue({
+      status: 200,
+      json: () => Promise.resolve(mockCreditBalance),
+    })
+
+    render(
+      <TestWrapper>
+        <PaymentPage />
+      </TestWrapper>
+    )
+
+    // 等待额度加载完成
+    await waitFor(() => {
+      // 额度不足时,额度支付选项不应该显示
+      expect(screen.queryByTestId('credit-payment-option')).not.toBeInTheDocument()
+    })
+
+    // 验证只有微信支付选项显示
+    expect(screen.getByTestId('wechat-payment-option')).toBeInTheDocument()
+  })
+
+  test('应该可以切换支付方式', async () => {
+    // Mock 额度查询返回正常数据
+    const mockCreditBalance = createTestCreditBalance()
+    ;(creditBalanceClient.me.$get as jest.Mock).mockResolvedValue({
+      status: 200,
+      json: () => Promise.resolve(mockCreditBalance),
+    })
+
+    render(
+      <TestWrapper>
+        <PaymentPage />
+      </TestWrapper>
+    )
+
+    // 等待额度加载完成
+    await waitFor(() => {
+      expect(screen.getByTestId('available-amount-text')).toHaveTextContent('使用信用额度支付')
+    })
+
+    // 初始应该是微信支付选中
+    const wechatOption = screen.getByTestId('wechat-payment-option')
+    expect(wechatOption).toHaveClass('border-blue-500')
+    expect(screen.getByTestId('wechat-selected')).toBeInTheDocument()
+
+    // 点击额度支付选项
+    const creditOption = screen.getByTestId('credit-payment-option')
+    fireEvent.click(creditOption)
+
+    // 验证额度支付被选中
+    await waitFor(() => {
+      expect(creditOption).toHaveClass('border-blue-500')
+      expect(screen.getByTestId('credit-selected')).toBeInTheDocument()
+    })
+
+    // 验证支付按钮文字变为额度支付
+    expect(screen.getByTestId('pay-button')).toHaveTextContent('额度支付 ¥100.00')
+  })
+
+  test('选择额度支付时应该显示额度说明(不显示额度详情)', async () => {
+    // Mock 额度查询返回正常数据
+    const mockCreditBalance = createTestCreditBalance()
+    ;(creditBalanceClient.me.$get as jest.Mock).mockResolvedValue({
+      status: 200,
+      json: () => Promise.resolve(mockCreditBalance),
+    })
+
+    render(
+      <TestWrapper>
+        <PaymentPage />
+      </TestWrapper>
+    )
+
+    // 等待额度加载完成
+    await waitFor(() => {
+      expect(screen.getByTestId('available-amount-text')).toHaveTextContent('使用信用额度支付')
+    })
+
+    // 点击额度支付选项
+    const creditOption = screen.getByTestId('credit-payment-option')
+    fireEvent.click(creditOption)
+
+    // 验证显示额度说明(不包含额度详情)
+    await waitFor(() => {
+      // 使用data-testid查询额度详情容器
+      const creditDetails = screen.getByTestId('credit-payment-details')
+      expect(creditDetails).toBeInTheDocument()
+
+      // 验证容器中包含基本说明(不包含额度详情)
+      expect(creditDetails).toHaveTextContent(/使用信用额度支付,无需立即付款/)
+      // 不应该包含额度详情
+      expect(creditDetails).not.toHaveTextContent(/总额度:/)
+      expect(creditDetails).not.toHaveTextContent(/已用额度:/)
+      expect(creditDetails).not.toHaveTextContent(/可用额度:/)
+    })
+  })
+
+  test('额度支付成功应该跳转到成功页面', async () => {
+    // Mock 额度查询返回正常数据
+    const mockCreditBalance = createTestCreditBalance()
+    ;(creditBalanceClient.me.$get as jest.Mock).mockResolvedValue({
+      status: 200,
+      json: () => Promise.resolve(mockCreditBalance),
+    })
+
+    // Mock 额度支付成功
+    const updatedBalance = createTestCreditBalance({ usedAmount: 300, availableAmount: 700 })
+    ;(creditBalanceClient.payment.$post as jest.Mock).mockResolvedValue({
+      status: 200,
+      json: () => Promise.resolve(updatedBalance),
+    })
+
+    render(
+      <TestWrapper>
+        <PaymentPage />
+      </TestWrapper>
+    )
+
+    // 等待额度加载完成
+    await waitFor(() => {
+      expect(screen.getByTestId('available-amount-text')).toHaveTextContent('使用信用额度支付')
+    })
+
+    // 点击额度支付选项
+    const creditOption = screen.getByTestId('credit-payment-option')
+    fireEvent.click(creditOption)
+
+    // 点击支付按钮
+    const payButton = screen.getByText('额度支付 ¥100.00')
+    fireEvent.click(payButton)
+
+    // 验证调用了额度支付API
+    await waitFor(() => {
+      expect(creditBalanceClient.payment.$post).toHaveBeenCalledWith({
+        json: {
+          referenceId: '123', // 现在传递订单ID而不是订单号
+          remark: '订单支付 - ORD123456',
+        },
+      })
+    })
+
+    // 验证跳转到成功页面
+    // 推进时间以触发setTimeout中的跳转
+    jest.advanceTimersByTime(1600)
+
+    await waitFor(() => {
+      expect(mockRedirectTo).toHaveBeenCalledWith({
+        url: '/pages/payment-success/index?orderId=123&amount=100&paymentMethod=credit',
+      })
+    })
+  })
+
+  test('额度支付失败应该显示错误信息', async () => {
+    // Mock 额度查询返回正常数据
+    const mockCreditBalance = createTestCreditBalance()
+    ;(creditBalanceClient.me.$get as jest.Mock).mockResolvedValue({
+      status: 200,
+      json: () => Promise.resolve(mockCreditBalance),
+    })
+
+    // Mock 额度支付失败
+    ;(creditBalanceClient.payment.$post as jest.Mock).mockResolvedValue({
+      status: 400,
+      json: () => Promise.resolve({ message: '额度不足' }),
+    })
+
+    render(
+      <TestWrapper>
+        <PaymentPage />
+      </TestWrapper>
+    )
+
+    // 等待额度加载完成
+    await waitFor(() => {
+      expect(screen.getByTestId('available-amount-text')).toHaveTextContent('使用信用额度支付')
+    })
+
+    // 点击额度支付选项
+    const creditOption = screen.getByTestId('credit-payment-option')
+    fireEvent.click(creditOption)
+
+    // 点击支付按钮
+    const payButton = screen.getByText('额度支付 ¥100.00')
+    fireEvent.click(payButton)
+
+    // 验证显示错误信息
+    await waitFor(() => {
+      expect(screen.getByText('额度不足')).toBeInTheDocument()
+    })
+  })
+
+  test('应该与微信支付选项并行工作', async () => {
+    // Mock 额度查询返回正常数据
+    const mockCreditBalance = createTestCreditBalance()
+    ;(creditBalanceClient.me.$get as jest.Mock).mockResolvedValue({
+      status: 200,
+      json: () => Promise.resolve(mockCreditBalance),
+    })
+
+    // Mock 微信支付参数
+    const { paymentClient } = require('@/api')
+    ;(paymentClient.payment.$post as jest.Mock).mockResolvedValue({
+      status: 200,
+      json: () => Promise.resolve(createTestPaymentData()),
+    })
+
+    render(
+      <TestWrapper>
+        <PaymentPage />
+      </TestWrapper>
+    )
+
+    // 等待额度加载完成
+    await waitFor(() => {
+      expect(screen.getByTestId('available-amount-text')).toHaveTextContent('使用信用额度支付')
+    })
+
+    // 验证两个支付选项都存在
+    expect(screen.getByTestId('wechat-payment-option')).toBeInTheDocument()
+    expect(screen.getByTestId('credit-payment-option')).toBeInTheDocument()
+
+    // 默认选中微信支付
+    const wechatOption = screen.getByTestId('wechat-payment-option')
+    expect(wechatOption).toHaveClass('border-blue-500')
+    expect(screen.getByTestId('wechat-selected')).toBeInTheDocument()
+    expect(screen.getByTestId('pay-button')).toHaveTextContent('微信支付 ¥100.00')
+
+    // 可以切换到额度支付
+    const creditOption = screen.getByTestId('credit-payment-option')
+    fireEvent.click(creditOption)
+
+    await waitFor(() => {
+      expect(creditOption).toHaveClass('border-blue-500')
+      expect(screen.getByTestId('credit-selected')).toBeInTheDocument()
+      expect(screen.getByTestId('pay-button')).toHaveTextContent('额度支付 ¥100.00')
+    })
+
+    // 可以切换回微信支付
+    fireEvent.click(wechatOption)
+
+    await waitFor(() => {
+      expect(wechatOption).toHaveClass('border-blue-500')
+      expect(screen.getByTestId('wechat-selected')).toBeInTheDocument()
+      expect(screen.getByTestId('pay-button')).toHaveTextContent('微信支付 ¥100.00')
+    })
+  })
+
+  test('页面加载时不应该自动调用微信支付API', async () => {
+    // Mock 额度查询返回正常数据
+    const mockCreditBalance = createTestCreditBalance()
+    ;(creditBalanceClient.me.$get as jest.Mock).mockResolvedValue({
+      status: 200,
+      json: () => Promise.resolve(mockCreditBalance),
+    })
+
+    // 获取paymentClient mock
+    const { paymentClient } = require('@/api')
+
+    render(
+      <TestWrapper>
+        <PaymentPage />
+      </TestWrapper>
+    )
+
+    // 等待额度加载完成
+    await waitFor(() => {
+      expect(screen.getByTestId('available-amount-text')).toHaveTextContent('使用信用额度支付')
+    })
+
+    // 验证微信支付API没有被调用(页面加载时不应该自动调用)
+    expect(paymentClient.payment.$post).not.toHaveBeenCalled()
+
+    // 验证支付按钮可用
+    const payButton = screen.getByTestId('pay-button')
+    expect(payButton).not.toBeDisabled()
+    expect(payButton).toHaveTextContent('微信支付 ¥100.00')
+  })
+
+  test('选择微信支付并点击支付按钮时才调用微信支付API', async () => {
+    // Mock 额度查询返回正常数据
+    const mockCreditBalance = createTestCreditBalance()
+    ;(creditBalanceClient.me.$get as jest.Mock).mockResolvedValue({
+      status: 200,
+      json: () => Promise.resolve(mockCreditBalance),
+    })
+
+    // Mock 微信支付参数
+    const { paymentClient } = require('@/api')
+    ;(paymentClient.payment.$post as jest.Mock).mockResolvedValue({
+      status: 200,
+      json: () => Promise.resolve(createTestPaymentData()),
+    })
+
+    render(
+      <TestWrapper>
+        <PaymentPage />
+      </TestWrapper>
+    )
+
+    // 等待额度加载完成
+    await waitFor(() => {
+      expect(screen.getByTestId('available-amount-text')).toHaveTextContent('使用信用额度支付')
+    })
+
+    // 初始时微信支付API不应该被调用
+    expect(paymentClient.payment.$post).not.toHaveBeenCalled()
+
+    // 点击支付按钮(默认选中微信支付)
+    const payButton = screen.getByTestId('pay-button')
+    fireEvent.click(payButton)
+
+    // 验证微信支付API被调用
+    await waitFor(() => {
+      expect(paymentClient.payment.$post).toHaveBeenCalledWith({
+        json: {
+          orderId: 123,
+          totalAmount: 10000, // 100元转换为分
+          description: '订单支付 - ORD123456',
+        },
+      })
+    })
+  })
+})